From ee6577d218d3ace70594db2c0ddf3d5e583a1b03 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Mon, 21 Nov 2022 17:12:35 +0100 Subject: [PATCH 01/29] WIP --- coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 35 +++++++++++++++++++++++--- coderd/database/queries/workspaces.sql | 34 ++++++++++++++++++++++--- coderd/workspaces.go | 2 +- coderd/workspaces_test.go | 20 +++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 0e553c601367c..97227dfa1d6e9 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -132,6 +132,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa arg.TemplateName, pq.Array(arg.TemplateIds), arg.Name, + arg.HasAgent, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e1c189934b90d..7e870026026f3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6342,18 +6342,29 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT + workspace_builds.job_id, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, provisioner_jobs.completed_at, - provisioner_jobs.error + provisioner_jobs.error, + workspace_agents.first_connected_at, + workspace_agents.last_connected_at FROM workspace_builds LEFT JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id + LEFT JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + LEFT JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id WHERE workspace_builds.workspace_id = workspaces.id ORDER BY @@ -6459,17 +6470,31 @@ WHERE name ILIKE '%' || $7 || '%' ELSE true END + -- Filter by agent status + AND CASE + WHEN $8 :: text != '' THEN + CASE + -- TODO timeout + WHEN $8 = 'connecting' THEN + latest_build.first_connected_at IS NULL + -- TODO disconnected + WHEN $8 = 'connected' THEN + latest_build.last_connected_at IS NOT NULL + ELSE true + END + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ORDER BY last_used_at DESC LIMIT CASE - WHEN $9 :: integer > 0 THEN - $9 + WHEN $10 :: integer > 0 THEN + $10 END OFFSET - $8 + $9 ` type GetWorkspacesParams struct { @@ -6480,6 +6505,7 @@ type GetWorkspacesParams struct { 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"` Offset int32 `db:"offset_" json:"offset_"` Limit int32 `db:"limit_" json:"limit_"` } @@ -6508,6 +6534,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) arg.TemplateName, pq.Array(arg.TemplateIds), arg.Name, + arg.HasAgent, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 071a970a66975..d27d3482fb09e 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -44,22 +44,36 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT + build_number, + workspace_builds.job_id, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, provisioner_jobs.completed_at, - provisioner_jobs.error + provisioner_jobs.error, + workspace_agents.first_connected_at, + workspace_agents.last_connected_at FROM workspace_builds LEFT JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id + LEFT JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + LEFT JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspaces.id + workspace_builds.workspace_id = '14260b33-a677-4399-8276-39ba287ec119' ORDER BY - build_number DESC + build_number DESC, + workspace_agents.last_connected_at ASC, + workspace_agents.first_connected_at ASC LIMIT 1 ) latest_build ON TRUE @@ -161,6 +175,20 @@ WHERE name ILIKE '%' || @name || '%' ELSE true END + -- Filter by agent status + AND CASE + WHEN @has_agent :: text != '' THEN + CASE + -- TODO timeout + WHEN @has_agent = 'connecting' THEN + latest_build.first_connected_at IS NULL + -- TODO disconnected + WHEN @has_agent = 'connected' THEN + latest_build.last_connected_at IS NOT NULL + ELSE true + END + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ORDER BY diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 67fdd76981445..cea91b70106c9 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1146,7 +1146,7 @@ func workspaceSearchQuery(query string, page codersdk.Pagination) (database.GetW filter.TemplateName = parser.String(searchParams, "", "template") filter.Name = parser.String(searchParams, "", "name") filter.Status = parser.String(searchParams, "", "status") - + filter.HasAgent = parser.String(searchParams, "", "has-agent") return filter, parser.Errors } diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 1711de1ba3413..4eaaccf16386f 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -824,6 +824,26 @@ func TestWorkspaceFilterManual(t *testing.T) { require.Len(t, res.Workspaces, 1) require.Equal(t, workspace.ID, res.Workspaces[0].ID) }) + t.Run("FilterQuery_HasAgent_Connecting", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user := coderdtest.CreateFirstUser(t, client) + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // single workspace + res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: fmt.Sprintf("has-agent:%s", "connected"), + }) + require.NoError(t, err) + require.Len(t, res.Workspaces, 0) + require.Equal(t, workspace.ID, res.Workspaces[0].ID) + }) } func TestOffsetLimit(t *testing.T) { From 7d61519c7635c3ab20412992c442340b14463e62 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 12:09:48 +0100 Subject: [PATCH 02/29] has-agent:connecting, connected --- coderd/database/databasefake/databasefake.go | 42 ++++++++++ coderd/database/queries.sql.go | 7 +- coderd/database/queries/workspaces.sql | 2 +- coderd/workspaces_test.go | 86 +++++++++++++++++++- 4 files changed, 130 insertions(+), 7 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 23572e1ad18a2..91f929b277688 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -871,6 +871,48 @@ 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 hasAgentValid bool + for _, wa := range workspaceAgents { + switch arg.HasAgent { + case "connected": + hasAgentValid = wa.LastConnectedAt.Valid + case "connecting": + hasAgentValid = !wa.FirstConnectedAt.Valid + } + break // only 1 agent is expected + } + + if !hasAgentValid { + continue + } + } + if len(arg.TemplateIds) > 0 { match := false for _, id := range arg.TemplateIds { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7e870026026f3..a5d46ab3ed6d5 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6342,6 +6342,7 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT + build_number, workspace_builds.job_id, workspace_builds.transition, provisioner_jobs.started_at, @@ -6366,9 +6367,11 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspaces.id + workspace_builds.workspace_id = workspace.id--'14260b33-a677-4399-8276-39ba287ec119' ORDER BY - build_number DESC + build_number DESC, + workspace_agents.last_connected_at ASC, + workspace_agents.first_connected_at ASC LIMIT 1 ) latest_build ON TRUE diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index d27d3482fb09e..10d44f8754097 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -69,7 +69,7 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = '14260b33-a677-4399-8276-39ba287ec119' + workspace_builds.workspace_id = workspace.id--'14260b33-a677-4399-8276-39ba287ec119' ORDER BY build_number DESC, workspace_agents.last_connected_at ASC, diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 4eaaccf16386f..de5a036e4d466 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -826,22 +826,100 @@ func TestWorkspaceFilterManual(t *testing.T) { }) t.Run("FilterQuery_HasAgent_Connecting", func(t *testing.T) { t.Parallel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) user := coderdtest.CreateFirstUser(t, client) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: fmt.Sprintf("has-agent:%s", "connecting"), + }) + require.NoError(t, err) + require.Len(t, res.Workspaces, 1) + require.Equal(t, workspace.ID, res.Workspaces[0].ID) + }) + t.Run("FilterQuery_HasAgent_Connected", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + agentClient := codersdk.New(client.URL) + agentClient.SetSessionToken(authToken) + agentCloser := agent.New(agent.Options{ + Client: agentClient, + Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug), + }) + defer func() { + _ = agentCloser.Close() + }() + + coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID) ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - // single workspace res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ FilterQuery: fmt.Sprintf("has-agent:%s", "connected"), }) require.NoError(t, err) - require.Len(t, res.Workspaces, 0) + require.Len(t, res.Workspaces, 1) require.Equal(t, workspace.ID, res.Workspaces[0].ID) }) } From b5a1ecc591c1f3640541c3ab91131816798340b1 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 12:12:25 +0100 Subject: [PATCH 03/29] Fix --- coderd/database/queries.sql.go | 2 +- coderd/database/queries/workspaces.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index a5d46ab3ed6d5..7f531c532da41 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6367,7 +6367,7 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspace.id--'14260b33-a677-4399-8276-39ba287ec119' + workspace_builds.workspace_id = workspace.id ORDER BY build_number DESC, workspace_agents.last_connected_at ASC, diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 10d44f8754097..cbd28dd281993 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -69,7 +69,7 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspace.id--'14260b33-a677-4399-8276-39ba287ec119' + workspace_builds.workspace_id = workspace.id ORDER BY build_number DESC, workspace_agents.last_connected_at ASC, From dca2b8b09a936196edf8ca22fd06861965d850b8 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 12:19:33 +0100 Subject: [PATCH 04/29] Fix --- coderd/database/queries.sql.go | 2 +- coderd/database/queries/workspaces.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 7f531c532da41..e32d3ff77ba66 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6367,7 +6367,7 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspace.id + workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC, workspace_agents.last_connected_at ASC, diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index cbd28dd281993..ccfc1d58c75ff 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -69,7 +69,7 @@ LEFT JOIN LATERAL ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspace.id + workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC, workspace_agents.last_connected_at ASC, From 0a4746cce43cdc4c39516468fe8b3615583ae7d5 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 13:59:22 +0100 Subject: [PATCH 05/29] has-agent:disconnected, timeout --- coderd/database/queries.sql.go | 12 +++++++++--- coderd/database/queries/workspaces.sql | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e32d3ff77ba66..837a301311680 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6350,8 +6350,11 @@ LEFT JOIN LATERAL ( provisioner_jobs.canceled_at, provisioner_jobs.completed_at, provisioner_jobs.error, + workspace_agents.created_at, + workspace_agents.disconnected_at, workspace_agents.first_connected_at, - workspace_agents.last_connected_at + workspace_agents.last_connected_at, + workspace_agents.connection_timeout_seconds FROM workspace_builds LEFT JOIN @@ -6477,10 +6480,13 @@ WHERE AND CASE WHEN $8 :: text != '' THEN CASE - -- TODO timeout + WHEN $8 = 'timeout' THEN + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) WHEN $8 = 'connecting' THEN latest_build.first_connected_at IS NULL - -- TODO disconnected + WHEN $8 = 'disconnected' THEN + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > workspace_agents.last_connected_at WHEN $8 = 'connected' THEN latest_build.last_connected_at IS NOT NULL ELSE true diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index ccfc1d58c75ff..0bce781f8a4d8 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -52,8 +52,11 @@ LEFT JOIN LATERAL ( provisioner_jobs.canceled_at, provisioner_jobs.completed_at, provisioner_jobs.error, + workspace_agents.created_at, + workspace_agents.disconnected_at, workspace_agents.first_connected_at, - workspace_agents.last_connected_at + workspace_agents.last_connected_at, + workspace_agents.connection_timeout_seconds FROM workspace_builds LEFT JOIN @@ -179,10 +182,13 @@ WHERE AND CASE WHEN @has_agent :: text != '' THEN CASE - -- TODO timeout + WHEN @has_agent = 'timeout' THEN + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN latest_build.first_connected_at IS NULL - -- TODO disconnected + WHEN @has_agent = 'disconnected' THEN + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > workspace_agents.last_connected_at WHEN @has_agent = 'connected' THEN latest_build.last_connected_at IS NOT NULL ELSE true From 70952e16cf9625f7cfc5318b2c106a5012ab17c3 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 14:09:57 +0100 Subject: [PATCH 06/29] Fix: typo --- coderd/database/queries.sql.go | 18 +++++++++--------- coderd/database/queries/workspaces.sql | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 837a301311680..be31902753168 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6478,18 +6478,18 @@ WHERE END -- Filter by agent status AND CASE - WHEN $8 :: text != '' THEN - CASE + WHEN $8 :: text != '' THEN + CASE WHEN $8 = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) - WHEN $8 = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) + WHEN $8 = 'connecting' THEN + latest_build.first_connected_at IS NULL WHEN $8 = 'disconnected' THEN - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > workspace_agents.last_connected_at + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > latest_build.last_connected_at WHEN $8 = 'connected' THEN - latest_build.last_connected_at IS NOT NULL - ELSE true + latest_build.last_connected_at IS NOT NULL + ELSE true END ELSE true END diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 0bce781f8a4d8..d5ce5f846bde8 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -180,18 +180,18 @@ WHERE END -- Filter by agent status AND CASE - WHEN @has_agent :: text != '' THEN - CASE + WHEN @has_agent :: text != '' THEN + CASE WHEN @has_agent = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) - WHEN @has_agent = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) + WHEN @has_agent = 'connecting' THEN + latest_build.first_connected_at IS NULL WHEN @has_agent = 'disconnected' THEN - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > workspace_agents.last_connected_at + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > latest_build.last_connected_at WHEN @has_agent = 'connected' THEN - latest_build.last_connected_at IS NOT NULL - ELSE true + latest_build.last_connected_at IS NOT NULL + ELSE true END ELSE true END From 7965a541a779fca9add476b8041065e1354f2eb9 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 14:34:13 +0100 Subject: [PATCH 07/29] Fix --- coderd/database/queries.sql.go | 6 ++++-- coderd/database/queries/workspaces.sql | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index be31902753168..e5ab17f5093a6 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6477,11 +6477,13 @@ WHERE 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 - CASE + latest_build.transition = 'start'::workspace_transition + AND CASE WHEN $8 = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN $8 = 'connecting' THEN latest_build.first_connected_at IS NULL WHEN $8 = 'disconnected' THEN diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index d5ce5f846bde8..f7fcc04e97553 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -179,11 +179,13 @@ WHERE 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 - CASE + latest_build.transition = 'start'::workspace_transition + AND CASE WHEN @has_agent = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * interval '1 second' < NOW()) + latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN latest_build.first_connected_at IS NULL WHEN @has_agent = 'disconnected' THEN From c1bd83944908f28c8e64ea6159fca7beea159105 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 14:45:37 +0100 Subject: [PATCH 08/29] TODOs --- coderd/database/databasefake/databasefake.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 91f929b277688..b50688631f4c8 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -905,6 +905,8 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. case "connecting": hasAgentValid = !wa.FirstConnectedAt.Valid } + // TODO disconnected + // TODO timeout break // only 1 agent is expected } From 2af41331ccc2264b02ac82bd6fd4a32c35a490c9 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 16:02:08 +0100 Subject: [PATCH 09/29] databasefake --- coderd/database/databasefake/databasefake.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index b50688631f4c8..7ac4f81df241a 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -904,9 +904,12 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. hasAgentValid = wa.LastConnectedAt.Valid case "connecting": hasAgentValid = !wa.FirstConnectedAt.Valid + case "disconnected": + hasAgentValid = wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time) + case "timeout": + hasAgentValid = !wa.FirstConnectedAt.Valid && + wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) } - // TODO disconnected - // TODO timeout break // only 1 agent is expected } From f9e2167976d0fcc6c840a83948cf4673f0a135e0 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 16:05:46 +0100 Subject: [PATCH 10/29] Fix: typo --- coderd/database/databasefake/databasefake.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 7ac4f81df241a..10434ad432ea6 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -897,23 +897,23 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. return nil, xerrors.Errorf("get workspace agents: %w", err) } - var hasAgentValid bool + var hasAgentMatched bool for _, wa := range workspaceAgents { switch arg.HasAgent { case "connected": - hasAgentValid = wa.LastConnectedAt.Valid + hasAgentMatched = wa.LastConnectedAt.Valid case "connecting": - hasAgentValid = !wa.FirstConnectedAt.Valid + hasAgentMatched = !wa.FirstConnectedAt.Valid case "disconnected": - hasAgentValid = wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time) + hasAgentMatched = wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time) case "timeout": - hasAgentValid = !wa.FirstConnectedAt.Valid && + hasAgentMatched = !wa.FirstConnectedAt.Valid && wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) } break // only 1 agent is expected } - if !hasAgentValid { + if !hasAgentMatched { continue } } From 45957c19feaeb703aa6cfc2686e1dce75c8d7c22 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 16:26:11 +0100 Subject: [PATCH 11/29] More TODOs --- coderd/database/queries.sql.go | 9 +++++++-- coderd/database/queries/workspaces.sql | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e5ab17f5093a6..07ae083b984ca 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6487,8 +6487,13 @@ WHERE WHEN $8 = 'connecting' THEN latest_build.first_connected_at IS NULL WHEN $8 = 'disconnected' THEN - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > latest_build.last_connected_at + ( + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > latest_build.last_connected_at + ) OR ( + latest_build.last_connected_at IS NOT NULL AND + latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- agentInactiveDisconnectTimeout = 6 + ) WHEN $8 = 'connected' THEN latest_build.last_connected_at IS NOT NULL ELSE true diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index f7fcc04e97553..9b3ae9f92d9d8 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -189,8 +189,13 @@ WHERE WHEN @has_agent = 'connecting' THEN latest_build.first_connected_at IS NULL WHEN @has_agent = 'disconnected' THEN - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > latest_build.last_connected_at + ( + latest_build.disconnected_at IS NOT NULL AND + latest_build.disconnected_at > latest_build.last_connected_at + ) OR ( + latest_build.last_connected_at IS NOT NULL AND + latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- agentInactiveDisconnectTimeout = 6 + ) WHEN @has_agent = 'connected' THEN latest_build.last_connected_at IS NOT NULL ELSE true From 66892d72bc376df82f624b9f76708a4769a4be5b Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 16:31:02 +0100 Subject: [PATCH 12/29] databasefake --- coderd/database/databasefake/databasefake.go | 4 +++- coderd/database/queries.sql.go | 2 +- coderd/database/queries/workspaces.sql | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 10434ad432ea6..a6fce4ef8f0a8 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -905,7 +905,9 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. case "connecting": hasAgentMatched = !wa.FirstConnectedAt.Valid case "disconnected": - hasAgentMatched = wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time) + // FIXME agentInactiveDisconnectTimeout = 6 + hasAgentMatched = (wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time)) || + (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(6*time.Second).Before(database.Now())) case "timeout": hasAgentMatched = !wa.FirstConnectedAt.Valid && wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 07ae083b984ca..ce4723dc86d89 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6492,7 +6492,7 @@ WHERE latest_build.disconnected_at > latest_build.last_connected_at ) OR ( latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- agentInactiveDisconnectTimeout = 6 + latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 ) WHEN $8 = 'connected' THEN latest_build.last_connected_at IS NOT NULL diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 9b3ae9f92d9d8..1e20c0051866d 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -194,7 +194,7 @@ WHERE latest_build.disconnected_at > latest_build.last_connected_at ) OR ( latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- agentInactiveDisconnectTimeout = 6 + latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 ) WHEN @has_agent = 'connected' THEN latest_build.last_connected_at IS NOT NULL From fc93f906897412995246b5e5910de28eed3d32fa Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 16:44:10 +0100 Subject: [PATCH 13/29] Timeout tests --- coderd/workspaces_test.go | 51 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index de5a036e4d466..28d84d67c4b55 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -824,7 +824,7 @@ func TestWorkspaceFilterManual(t *testing.T) { require.Len(t, res.Workspaces, 1) require.Equal(t, workspace.ID, res.Workspaces[0].ID) }) - t.Run("FilterQuery_HasAgent_Connecting", func(t *testing.T) { + t.Run("FilterQueryHasAgentConnecting", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ @@ -867,7 +867,7 @@ func TestWorkspaceFilterManual(t *testing.T) { require.Len(t, res.Workspaces, 1) require.Equal(t, workspace.ID, res.Workspaces[0].ID) }) - t.Run("FilterQuery_HasAgent_Connected", func(t *testing.T) { + t.Run("FilterQueryHasAgentConnected", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ @@ -922,6 +922,53 @@ func TestWorkspaceFilterManual(t *testing.T) { require.Len(t, res.Workspaces, 1) require.Equal(t, workspace.ID, res.Workspaces[0].ID) }) + t.Run("FilterQueryHasAgentTimeout", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: authToken, + }, + ConnectionTimeoutSeconds: 1, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) + defer cancel() + + testutil.Eventually(ctx, t, func(ctx context.Context) (done bool) { + workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: fmt.Sprintf("has-agent:%s", "timeout"), + }) + if !assert.NoError(t, err) { + return false + } + return workspaces.Count == 1 + }, testutil.IntervalMedium, "agent status timeout") + }) } func TestOffsetLimit(t *testing.T) { From e587b5d555d2a3b40930838f110d8375f5676013 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Tue, 22 Nov 2022 19:03:40 +0100 Subject: [PATCH 14/29] Address PR comments --- coderd/database/queries.sql.go | 30 +++++++++++++------------- coderd/database/queries/workspaces.sql | 30 +++++++++++++------------- coderd/workspaces_test.go | 4 +--- 3 files changed, 31 insertions(+), 33 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ce4723dc86d89..089ccd5c503e4 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6342,8 +6342,8 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT - build_number, - workspace_builds.job_id, + build_number, + workspace_builds.job_id, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, @@ -6362,13 +6362,13 @@ LEFT JOIN LATERAL ( ON provisioner_jobs.id = workspace_builds.job_id LEFT JOIN - workspace_resources + workspace_resources ON - workspace_resources.job_id = provisioner_jobs.id + workspace_resources.job_id = provisioner_jobs.id LEFT JOIN - workspace_agents + workspace_agents ON - workspace_agents.resource_id = workspace_resources.id + workspace_agents.resource_id = workspace_resources.id WHERE workspace_builds.workspace_id = workspaces.id ORDER BY @@ -6461,7 +6461,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 @@ -6485,7 +6485,7 @@ WHERE WHEN $8 = 'timeout' THEN latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN $8 = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.first_connected_at IS NULL WHEN $8 = 'disconnected' THEN ( latest_build.disconnected_at IS NOT NULL AND @@ -6495,7 +6495,7 @@ WHERE latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 ) WHEN $8 = 'connected' THEN - latest_build.last_connected_at IS NOT NULL + latest_build.last_connected_at IS NOT NULL ELSE true END ELSE true @@ -6503,14 +6503,14 @@ WHERE -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ORDER BY - last_used_at DESC + last_used_at DESC LIMIT - CASE - WHEN $10 :: integer > 0 THEN - $10 - END + CASE + WHEN $10 :: integer > 0 THEN + $10 + END OFFSET - $9 + $9 ` type GetWorkspacesParams struct { diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 1e20c0051866d..5ed5334e4fe08 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -44,8 +44,8 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT - build_number, - workspace_builds.job_id, + build_number, + workspace_builds.job_id, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, @@ -64,13 +64,13 @@ LEFT JOIN LATERAL ( ON provisioner_jobs.id = workspace_builds.job_id LEFT JOIN - workspace_resources + workspace_resources ON - workspace_resources.job_id = provisioner_jobs.id + workspace_resources.job_id = provisioner_jobs.id LEFT JOIN - workspace_agents + workspace_agents ON - workspace_agents.resource_id = workspace_resources.id + workspace_agents.resource_id = workspace_resources.id WHERE workspace_builds.workspace_id = workspaces.id ORDER BY @@ -163,7 +163,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 @@ -187,7 +187,7 @@ WHERE WHEN @has_agent = 'timeout' THEN latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.first_connected_at IS NULL WHEN @has_agent = 'disconnected' THEN ( latest_build.disconnected_at IS NOT NULL AND @@ -197,7 +197,7 @@ WHERE latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 ) WHEN @has_agent = 'connected' THEN - latest_build.last_connected_at IS NOT NULL + latest_build.last_connected_at IS NOT NULL ELSE true END ELSE true @@ -205,14 +205,14 @@ WHERE -- 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 diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 28d84d67c4b55..f7087ac304eed 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -963,9 +963,7 @@ func TestWorkspaceFilterManual(t *testing.T) { workspaces, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ FilterQuery: fmt.Sprintf("has-agent:%s", "timeout"), }) - if !assert.NoError(t, err) { - return false - } + require.NoError(t, err) return workspaces.Count == 1 }, testutil.IntervalMedium, "agent status timeout") }) From 458a8ebad69d80649bbf2df2cfa2bcfc19b89113 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 10:11:21 +0100 Subject: [PATCH 15/29] Implement FIXMEs --- coderd/database/databasefake/databasefake.go | 3 +- coderd/database/modelqueries.go | 1 + coderd/database/queries.sql.go | 30 +++++++++++--------- coderd/database/queries/workspaces.sql | 2 +- coderd/workspaces.go | 6 ++-- coderd/workspaces_internal_test.go | 12 +++++++- 6 files changed, 34 insertions(+), 20 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index a6fce4ef8f0a8..45c311eefc655 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -905,9 +905,8 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. case "connecting": hasAgentMatched = !wa.FirstConnectedAt.Valid case "disconnected": - // FIXME agentInactiveDisconnectTimeout = 6 hasAgentMatched = (wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time)) || - (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(6*time.Second).Before(database.Now())) + (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(time.Duration(arg.AgentInactiveDisconnectTimeout)*time.Second).Before(database.Now())) case "timeout": hasAgentMatched = !wa.FirstConnectedAt.Valid && wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 97227dfa1d6e9..41c4db732451b 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -133,6 +133,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, + arg.AgentInactiveDisconnectTimeout, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 089ccd5c503e4..de3ea6d865bfe 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6492,7 +6492,7 @@ WHERE latest_build.disconnected_at > latest_build.last_connected_at ) OR ( latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 + latest_build.last_connected_at + INTERVAL '1 second' * $9 :: bigint < NOW() ) WHEN $8 = 'connected' THEN latest_build.last_connected_at IS NOT NULL @@ -6506,24 +6506,25 @@ ORDER BY last_used_at DESC LIMIT CASE - WHEN $10 :: integer > 0 THEN - $10 + WHEN $11 :: integer > 0 THEN + $11 END OFFSET - $9 + $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"` - HasAgent string `db:"has_agent" json:"has_agent"` - 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"` + AgentInactiveDisconnectTimeout int64 `db:"agent_inactive_disconnect_timeout" json:"agent_inactive_disconnect_timeout"` + Offset int32 `db:"offset_" json:"offset_"` + Limit int32 `db:"limit_" json:"limit_"` } type GetWorkspacesRow struct { @@ -6551,6 +6552,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, + arg.AgentInactiveDisconnectTimeout, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 5ed5334e4fe08..25dcf488849b0 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -194,7 +194,7 @@ WHERE latest_build.disconnected_at > latest_build.last_connected_at ) OR ( latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + 6 * INTERVAL '1 second' < NOW() -- FIXME agentInactiveDisconnectTimeout = 6 + latest_build.last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout :: bigint < NOW() ) WHEN @has_agent = 'connected' THEN latest_build.last_connected_at IS NOT NULL diff --git a/coderd/workspaces.go b/coderd/workspaces.go index cea91b70106c9..e6f1e0357c4d8 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -104,7 +104,7 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { } queryStr := r.URL.Query().Get("q") - filter, errs := workspaceSearchQuery(queryStr, page) + filter, errs := workspaceSearchQuery(queryStr, page, api.AgentInactiveDisconnectTimeout) if len(errs) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid workspace search query.", @@ -1098,8 +1098,10 @@ func validWorkspaceSchedule(s *string) (sql.NullString, error) { // workspaceSearchQuery takes a query string and returns the workspace filter. // It also can return the list of validation errors to return to the api. -func workspaceSearchQuery(query string, page codersdk.Pagination) (database.GetWorkspacesParams, []codersdk.ValidationError) { +func workspaceSearchQuery(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, []codersdk.ValidationError) { filter := database.GetWorkspacesParams{ + AgentInactiveDisconnectTimeout: int64(agentInactiveDisconnectTimeout.Seconds()), + Offset: int32(page.Offset), Limit: int32(page.Limit), } diff --git a/coderd/workspaces_internal_test.go b/coderd/workspaces_internal_test.go index 03a74b29f00ae..dde0dafb45475 100644 --- a/coderd/workspaces_internal_test.go +++ b/coderd/workspaces_internal_test.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "testing" + "time" "github.com/coder/coder/coderd/database" "github.com/coder/coder/codersdk" @@ -136,7 +137,7 @@ func TestSearchWorkspace(t *testing.T) { c := c t.Run(c.Name, func(t *testing.T) { t.Parallel() - values, errs := workspaceSearchQuery(c.Query, codersdk.Pagination{}) + values, errs := workspaceSearchQuery(c.Query, codersdk.Pagination{}, 0) if c.ExpectedErrorContains != "" { require.True(t, len(errs) > 0, "expect some errors") var s strings.Builder @@ -150,4 +151,13 @@ func TestSearchWorkspace(t *testing.T) { } }) } + t.Run("AgentInactiveDisconnectTimeout", func(t *testing.T) { + t.Parallel() + + query := `foo:bar` + timeout := 1337 * time.Second + values, errs := workspaceSearchQuery(query, codersdk.Pagination{}, timeout) + require.Empty(t, errs) + require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeout) + }) } From 81d9fef82b1e950fbfa460ec5c0f52738dbe55de Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 12:13:38 +0100 Subject: [PATCH 16/29] Renamings --- coderd/database/databasefake/databasefake.go | 2 +- coderd/database/modelqueries.go | 2 +- coderd/database/queries.sql.go | 52 ++++++++++---------- coderd/database/queries/workspaces.sql | 28 +++++------ coderd/workspaces.go | 2 +- coderd/workspaces_internal_test.go | 2 +- 6 files changed, 44 insertions(+), 44 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 45c311eefc655..cea28d16f20d1 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -906,7 +906,7 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. hasAgentMatched = !wa.FirstConnectedAt.Valid case "disconnected": hasAgentMatched = (wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time)) || - (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(time.Duration(arg.AgentInactiveDisconnectTimeout)*time.Second).Before(database.Now())) + (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(time.Duration(arg.AgentInactiveDisconnectTimeoutSeconds)*time.Second).Before(database.Now())) case "timeout": hasAgentMatched = !wa.FirstConnectedAt.Valid && wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 41c4db732451b..5b4fc7f5f3078 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -133,7 +133,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, - arg.AgentInactiveDisconnectTimeout, + arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index de3ea6d865bfe..ba3929c90889e 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6350,11 +6350,11 @@ LEFT JOIN LATERAL ( provisioner_jobs.canceled_at, provisioner_jobs.completed_at, provisioner_jobs.error, - workspace_agents.created_at, - workspace_agents.disconnected_at, - workspace_agents.first_connected_at, - workspace_agents.last_connected_at, - workspace_agents.connection_timeout_seconds + workspace_agents.created_at AS agent_created_at, + workspace_agents.disconnected_at AS agent_disconnected_at, + workspace_agents.first_connected_at AS agent_first_connected_at, + workspace_agents.last_connected_at AS agent_last_connected_at, + workspace_agents.connection_timeout_seconds AS agent_connection_timeout_seconds FROM workspace_builds LEFT JOIN @@ -6373,8 +6373,8 @@ LEFT JOIN LATERAL ( workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC, - workspace_agents.last_connected_at ASC, - workspace_agents.first_connected_at ASC + agent_last_connected_at ASC, + agent_first_connected_at ASC LIMIT 1 ) latest_build ON TRUE @@ -6483,19 +6483,19 @@ WHERE latest_build.transition = 'start'::workspace_transition AND CASE WHEN $8 = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) + latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN $8 = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.agent_first_connected_at IS NULL WHEN $8 = 'disconnected' THEN ( - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > latest_build.last_connected_at + latest_build.agent_disconnected_at IS NOT NULL AND + latest_build.agent_disconnected_at > latest_build.agent_last_connected_at ) OR ( - latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + INTERVAL '1 second' * $9 :: bigint < NOW() + latest_build.agent_last_connected_at IS NOT NULL AND + latest_build.agent_last_connected_at + INTERVAL '1 second' * $9 :: bigint < NOW() ) WHEN $8 = 'connected' THEN - latest_build.last_connected_at IS NOT NULL + latest_build.agent_last_connected_at IS NOT NULL ELSE true END ELSE true @@ -6514,17 +6514,17 @@ OFFSET ` 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"` - HasAgent string `db:"has_agent" json:"has_agent"` - AgentInactiveDisconnectTimeout int64 `db:"agent_inactive_disconnect_timeout" json:"agent_inactive_disconnect_timeout"` - 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 { @@ -6552,7 +6552,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, - arg.AgentInactiveDisconnectTimeout, + arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, ) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 25dcf488849b0..1e3302ad7b5c5 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -52,11 +52,11 @@ LEFT JOIN LATERAL ( provisioner_jobs.canceled_at, provisioner_jobs.completed_at, provisioner_jobs.error, - workspace_agents.created_at, - workspace_agents.disconnected_at, - workspace_agents.first_connected_at, - workspace_agents.last_connected_at, - workspace_agents.connection_timeout_seconds + workspace_agents.created_at AS agent_created_at, + workspace_agents.disconnected_at AS agent_disconnected_at, + workspace_agents.first_connected_at AS agent_first_connected_at, + workspace_agents.last_connected_at AS agent_last_connected_at, + workspace_agents.connection_timeout_seconds AS agent_connection_timeout_seconds FROM workspace_builds LEFT JOIN @@ -75,8 +75,8 @@ LEFT JOIN LATERAL ( workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC, - workspace_agents.last_connected_at ASC, - workspace_agents.first_connected_at ASC + agent_last_connected_at ASC, + agent_first_connected_at ASC LIMIT 1 ) latest_build ON TRUE @@ -185,19 +185,19 @@ WHERE latest_build.transition = 'start'::workspace_transition AND CASE WHEN @has_agent = 'timeout' THEN - latest_build.first_connected_at IS NULL AND (latest_build.created_at + latest_build.connection_timeout_seconds * INTERVAL '1 second' < NOW()) + latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN - latest_build.first_connected_at IS NULL + latest_build.agent_first_connected_at IS NULL WHEN @has_agent = 'disconnected' THEN ( - latest_build.disconnected_at IS NOT NULL AND - latest_build.disconnected_at > latest_build.last_connected_at + latest_build.agent_disconnected_at IS NOT NULL AND + latest_build.agent_disconnected_at > latest_build.agent_last_connected_at ) OR ( - latest_build.last_connected_at IS NOT NULL AND - latest_build.last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout :: bigint < NOW() + latest_build.agent_last_connected_at IS NOT NULL AND + latest_build.agent_last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint < NOW() ) WHEN @has_agent = 'connected' THEN - latest_build.last_connected_at IS NOT NULL + latest_build.agent_last_connected_at IS NOT NULL ELSE true END ELSE true diff --git a/coderd/workspaces.go b/coderd/workspaces.go index e6f1e0357c4d8..a6ad0c6adf294 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1100,7 +1100,7 @@ func validWorkspaceSchedule(s *string) (sql.NullString, error) { // It also can return the list of validation errors to return to the api. func workspaceSearchQuery(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, []codersdk.ValidationError) { filter := database.GetWorkspacesParams{ - AgentInactiveDisconnectTimeout: int64(agentInactiveDisconnectTimeout.Seconds()), + AgentInactiveDisconnectTimeoutSeconds: int64(agentInactiveDisconnectTimeout.Seconds()), Offset: int32(page.Offset), Limit: int32(page.Limit), diff --git a/coderd/workspaces_internal_test.go b/coderd/workspaces_internal_test.go index dde0dafb45475..3cfa8ead5665d 100644 --- a/coderd/workspaces_internal_test.go +++ b/coderd/workspaces_internal_test.go @@ -158,6 +158,6 @@ func TestSearchWorkspace(t *testing.T) { timeout := 1337 * time.Second values, errs := workspaceSearchQuery(query, codersdk.Pagination{}, timeout) require.Empty(t, errs) - require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeout) + require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeoutSeconds) }) } From 5ca8c175025567294ee2c570f2558b42d4301bbd Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 13:05:26 +0100 Subject: [PATCH 17/29] Address PR comments --- coderd/database/databasefake/databasefake.go | 46 +++++++++++++++----- coderd/database/queries.sql.go | 10 ++++- coderd/database/queries/workspaces.sql | 10 ++++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index cea28d16f20d1..0c7e6beb93479 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -899,18 +899,8 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. var hasAgentMatched bool for _, wa := range workspaceAgents { - switch arg.HasAgent { - case "connected": - hasAgentMatched = wa.LastConnectedAt.Valid - case "connecting": - hasAgentMatched = !wa.FirstConnectedAt.Valid - case "disconnected": - hasAgentMatched = (wa.DisconnectedAt.Valid && wa.DisconnectedAt.Time.After(wa.LastConnectedAt.Time)) || - (wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Add(time.Duration(arg.AgentInactiveDisconnectTimeoutSeconds)*time.Second).Before(database.Now())) - case "timeout": - hasAgentMatched = !wa.FirstConnectedAt.Valid && - wa.CreatedAt.Add(time.Duration(wa.ConnectionTimeoutSeconds)*time.Second).Before(database.Now()) - } + mapped := mapAgentStatus(wa, arg.AgentInactiveDisconnectTimeoutSeconds) + hasAgentMatched = mapped == arg.HasAgent break // only 1 agent is expected } @@ -957,6 +947,38 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. return convertToWorkspaceRows(workspaces, int64(beforePageCount)), nil } +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 { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ba3929c90889e..5e2d0f265ff26 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6485,7 +6485,7 @@ WHERE WHEN $8 = 'timeout' THEN latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN $8 = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL + latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) WHEN $8 = 'disconnected' THEN ( latest_build.agent_disconnected_at IS NOT NULL AND @@ -6495,7 +6495,13 @@ WHERE latest_build.agent_last_connected_at + INTERVAL '1 second' * $9 :: bigint < NOW() ) WHEN $8 = 'connected' THEN - latest_build.agent_last_connected_at IS NOT NULL + ( + latest_build.agent_disconnected_at IS NOT NULL AND + latest_build.agent_disconnected_at <= latest_build.agent_last_connected_at + ) OR ( + latest_build.agent_last_connected_at IS NOT NULL AND + latest_build.agent_last_connected_at + INTERVAL '1 second' * $9 :: bigint >= NOW() + ) ELSE true END ELSE true diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 1e3302ad7b5c5..064994bb4593f 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -187,7 +187,7 @@ WHERE WHEN @has_agent = 'timeout' THEN latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL + latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) WHEN @has_agent = 'disconnected' THEN ( latest_build.agent_disconnected_at IS NOT NULL AND @@ -197,7 +197,13 @@ WHERE latest_build.agent_last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint < NOW() ) WHEN @has_agent = 'connected' THEN - latest_build.agent_last_connected_at IS NOT NULL + ( + latest_build.agent_disconnected_at IS NOT NULL AND + latest_build.agent_disconnected_at <= latest_build.agent_last_connected_at + ) OR ( + latest_build.agent_last_connected_at IS NOT NULL AND + latest_build.agent_last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint >= NOW() + ) ELSE true END ELSE true From ec2d571a4af889604a06c3d713d57e4a936ccddf Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 13:13:15 +0100 Subject: [PATCH 18/29] Fix: readability --- coderd/database/queries.sql.go | 6 ++++-- coderd/database/queries/workspaces.sql | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 5e2d0f265ff26..ae70179de2a2f 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6483,9 +6483,11 @@ WHERE latest_build.transition = 'start'::workspace_transition AND CASE WHEN $8 = 'timeout' THEN - latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) + latest_build.agent_first_connected_at IS NULL AND + (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN $8 = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) + latest_build.agent_first_connected_at IS NULL AND + (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) WHEN $8 = 'disconnected' THEN ( latest_build.agent_disconnected_at IS NOT NULL AND diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 064994bb4593f..81ebe1ed88f46 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -185,9 +185,11 @@ WHERE latest_build.transition = 'start'::workspace_transition AND CASE WHEN @has_agent = 'timeout' THEN - latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) + latest_build.agent_first_connected_at IS NULL AND + (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) WHEN @has_agent = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL AND (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) + latest_build.agent_first_connected_at IS NULL AND + (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) WHEN @has_agent = 'disconnected' THEN ( latest_build.agent_disconnected_at IS NOT NULL AND From 4b2f8315873ca0dd00176d874d2058ee48c5b893 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 13:37:19 +0100 Subject: [PATCH 19/29] Fix: refactor CASE logic --- coderd/database/queries.sql.go | 38 ++++++++++---------------- coderd/database/queries/workspaces.sql | 38 ++++++++++---------------- 2 files changed, 30 insertions(+), 46 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ae70179de2a2f..e120e35d0efa3 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6482,29 +6482,21 @@ WHERE WHEN $8 :: text != '' THEN latest_build.transition = 'start'::workspace_transition AND CASE - WHEN $8 = 'timeout' THEN - latest_build.agent_first_connected_at IS NULL AND - (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) - WHEN $8 = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL AND - (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) - WHEN $8 = 'disconnected' THEN - ( - latest_build.agent_disconnected_at IS NOT NULL AND - latest_build.agent_disconnected_at > latest_build.agent_last_connected_at - ) OR ( - latest_build.agent_last_connected_at IS NOT NULL AND - latest_build.agent_last_connected_at + INTERVAL '1 second' * $9 :: bigint < NOW() - ) - WHEN $8 = 'connected' THEN - ( - latest_build.agent_disconnected_at IS NOT NULL AND - latest_build.agent_disconnected_at <= latest_build.agent_last_connected_at - ) OR ( - latest_build.agent_last_connected_at IS NOT NULL AND - latest_build.agent_last_connected_at + INTERVAL '1 second' * $9 :: bigint >= NOW() - ) - ELSE true + WHEN latest_build.agent_first_connected_at IS NULL THEN + CASE + WHEN latest_build.agent_connection_timeout_seconds > 0 AND NOW() - latest_build.agent_created_at > latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' THEN + CASE WHEN $8 :: text = 'timeout' THEN true ELSE false END + ELSE + CASE WHEN $8 :: text = 'connecting' THEN true ELSE false END + END + WHEN latest_build.agent_disconnected_at > latest_build.agent_last_connected_at THEN + CASE WHEN $8 :: text = 'disconnected' THEN true ELSE false END + WHEN NOW() - latest_build.agent_last_connected_at > INTERVAL '1 second' * $9 :: bigint THEN + CASE WHEN $8 :: text = 'disconnected' THEN true ELSE false END + WHEN latest_build.agent_last_connected_at IS NOT NULL THEN + CASE WHEN $8 :: text = 'connected' THEN true ELSE false END + ELSE + true END ELSE true END diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 81ebe1ed88f46..f3cd10e71b2b2 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -184,29 +184,21 @@ WHERE WHEN @has_agent :: text != '' THEN latest_build.transition = 'start'::workspace_transition AND CASE - WHEN @has_agent = 'timeout' THEN - latest_build.agent_first_connected_at IS NULL AND - (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' < NOW()) - WHEN @has_agent = 'connecting' THEN - latest_build.agent_first_connected_at IS NULL AND - (latest_build.agent_created_at + latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' >= NOW()) - WHEN @has_agent = 'disconnected' THEN - ( - latest_build.agent_disconnected_at IS NOT NULL AND - latest_build.agent_disconnected_at > latest_build.agent_last_connected_at - ) OR ( - latest_build.agent_last_connected_at IS NOT NULL AND - latest_build.agent_last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint < NOW() - ) - WHEN @has_agent = 'connected' THEN - ( - latest_build.agent_disconnected_at IS NOT NULL AND - latest_build.agent_disconnected_at <= latest_build.agent_last_connected_at - ) OR ( - latest_build.agent_last_connected_at IS NOT NULL AND - latest_build.agent_last_connected_at + INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint >= NOW() - ) - ELSE true + WHEN latest_build.agent_first_connected_at IS NULL THEN + CASE + WHEN latest_build.agent_connection_timeout_seconds > 0 AND NOW() - latest_build.agent_created_at > latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' THEN + CASE WHEN @has_agent :: text = 'timeout' THEN true ELSE false END + ELSE + CASE WHEN @has_agent :: text = 'connecting' THEN true ELSE false END + END + WHEN latest_build.agent_disconnected_at > latest_build.agent_last_connected_at THEN + CASE WHEN @has_agent :: text = 'disconnected' THEN true ELSE false END + WHEN NOW() - latest_build.agent_last_connected_at > INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint THEN + CASE WHEN @has_agent :: text = 'disconnected' THEN true ELSE false END + WHEN latest_build.agent_last_connected_at IS NOT NULL THEN + CASE WHEN @has_agent :: text = 'connected' THEN true ELSE false END + ELSE + true END ELSE true END From 3c0d4fefab4d89b0d4ba6bacf070815e4da083f6 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 13:40:03 +0100 Subject: [PATCH 20/29] CASE logic --- coderd/database/queries.sql.go | 1 + coderd/database/queries/workspaces.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e120e35d0efa3..296ba95074eca 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6478,6 +6478,7 @@ WHERE END -- Filter by agent status -- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents. + -- The following CASE statement reflects the conditional logic in coderd/workspaceagents.go AND CASE WHEN $8 :: text != '' THEN latest_build.transition = 'start'::workspace_transition diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index f3cd10e71b2b2..ee6f9686d35c9 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -180,6 +180,7 @@ WHERE END -- Filter by agent status -- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents. + -- The following CASE statement reflects the conditional logic in coderd/workspaceagents.go AND CASE WHEN @has_agent :: text != '' THEN latest_build.transition = 'start'::workspace_transition From 9067a9e3dc841aa912daf376c2e7ae4669822ec9 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 14:09:00 +0100 Subject: [PATCH 21/29] Fix --- coderd/database/queries.sql.go | 2 +- coderd/database/queries/workspaces.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 296ba95074eca..b7db6e46329f6 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6497,7 +6497,7 @@ WHERE WHEN latest_build.agent_last_connected_at IS NOT NULL THEN CASE WHEN $8 :: text = 'connected' THEN true ELSE false END ELSE - true + false END ELSE true END diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index ee6f9686d35c9..e4028b1978652 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -199,7 +199,7 @@ WHERE WHEN latest_build.agent_last_connected_at IS NOT NULL THEN CASE WHEN @has_agent :: text = 'connected' THEN true ELSE false END ELSE - true + false END ELSE true END From c14663c0c7dab22d94f0b03788ec42fc617d017e Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 17:28:05 +0100 Subject: [PATCH 22/29] Use CTE --- coderd/database/modelqueries.go | 2 +- coderd/database/queries.sql.go | 103 ++++++++++++++----------- coderd/database/queries/workspaces.sql | 93 ++++++++++++---------- 3 files changed, 112 insertions(+), 86 deletions(-) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 5b4fc7f5f3078..9f090509740c3 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -133,9 +133,9 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, - arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, + arg.AgentInactiveDisconnectTimeoutSeconds, ) if err != nil { return nil, xerrors.Errorf("get authorized workspaces: %w", err) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index b7db6e46329f6..5c7bd7c4ddf02 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6336,47 +6336,72 @@ func (q *sqlQuerier) GetWorkspaceOwnerCountsByTemplateIDs(ctx context.Context, i } const getWorkspaces = `-- name: GetWorkspaces :many +WITH workspace_builds_agents AS ( + SELECT + workspace_builds.workspace_id AS workspace_id, + workspace_builds.build_number AS build_number, + workspace_agents.id AS agent_id, + ( + 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' * $11 :: bigint THEN + 'disconnected' + WHEN workspace_agents.last_connected_at IS NOT NULL THEN + 'connected' + ELSE + NULL + END + ) AS agent_status + FROM + workspace_builds + LEFT JOIN + provisioner_jobs + ON + provisioner_jobs.id = workspace_builds.job_id + LEFT JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + LEFT JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id + WHERE + workspace_builds.transition = 'start'::workspace_transition AND + workspace_agents.id IS NOT NULL +) SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, COUNT(*) OVER () as count FROM workspaces LEFT JOIN LATERAL ( SELECT - build_number, - workspace_builds.job_id, + workspace_builds.build_number, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, provisioner_jobs.completed_at, - provisioner_jobs.error, - workspace_agents.created_at AS agent_created_at, - workspace_agents.disconnected_at AS agent_disconnected_at, - workspace_agents.first_connected_at AS agent_first_connected_at, - workspace_agents.last_connected_at AS agent_last_connected_at, - workspace_agents.connection_timeout_seconds AS agent_connection_timeout_seconds + provisioner_jobs.error FROM workspace_builds LEFT JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id - LEFT JOIN - workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id - LEFT JOIN - workspace_agents - ON - workspace_agents.resource_id = workspace_resources.id WHERE workspace_builds.workspace_id = workspaces.id ORDER BY - build_number DESC, - agent_last_connected_at ASC, - agent_first_connected_at ASC - LIMIT - 1 + build_number DESC + LIMIT 1 ) latest_build ON TRUE WHERE -- Optionally include deleted workspaces @@ -6478,27 +6503,15 @@ WHERE END -- Filter by agent status -- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents. - -- The following CASE statement reflects the conditional logic in coderd/workspaceagents.go AND CASE WHEN $8 :: text != '' THEN - latest_build.transition = 'start'::workspace_transition - AND CASE - WHEN latest_build.agent_first_connected_at IS NULL THEN - CASE - WHEN latest_build.agent_connection_timeout_seconds > 0 AND NOW() - latest_build.agent_created_at > latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' THEN - CASE WHEN $8 :: text = 'timeout' THEN true ELSE false END - ELSE - CASE WHEN $8 :: text = 'connecting' THEN true ELSE false END - END - WHEN latest_build.agent_disconnected_at > latest_build.agent_last_connected_at THEN - CASE WHEN $8 :: text = 'disconnected' THEN true ELSE false END - WHEN NOW() - latest_build.agent_last_connected_at > INTERVAL '1 second' * $9 :: bigint THEN - CASE WHEN $8 :: text = 'disconnected' THEN true ELSE false END - WHEN latest_build.agent_last_connected_at IS NOT NULL THEN - CASE WHEN $8 :: text = 'connected' THEN true ELSE false END - ELSE - false - END + ( + SELECT COUNT(*) FROM workspace_builds_agents + WHERE + workspace_builds_agents.workspace_id = workspaces.id AND + workspace_builds_agents.build_number = latest_build.build_number AND + agent_status = $8 + ) > 0 ELSE true END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces @@ -6507,11 +6520,11 @@ ORDER BY last_used_at DESC LIMIT CASE - WHEN $11 :: integer > 0 THEN - $11 + WHEN $10 :: integer > 0 THEN + $10 END OFFSET - $10 + $9 ` type GetWorkspacesParams struct { @@ -6523,9 +6536,9 @@ type GetWorkspacesParams struct { 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_"` + AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"` } type GetWorkspacesRow struct { @@ -6553,9 +6566,9 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, - arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, + arg.AgentInactiveDisconnectTimeoutSeconds, ) if err != nil { return nil, err diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index e4028b1978652..bbbfd31bc8b42 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -38,47 +38,72 @@ WHERE ); -- name: GetWorkspaces :many +WITH workspace_builds_agents AS ( + SELECT + workspace_builds.workspace_id AS workspace_id, + workspace_builds.build_number AS build_number, + workspace_agents.id AS agent_id, + ( + 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 + ) AS agent_status + FROM + workspace_builds + LEFT JOIN + provisioner_jobs + ON + provisioner_jobs.id = workspace_builds.job_id + LEFT JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + LEFT JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id + WHERE + workspace_builds.transition = 'start'::workspace_transition AND + workspace_agents.id IS NOT NULL +) SELECT workspaces.*, COUNT(*) OVER () as count FROM workspaces LEFT JOIN LATERAL ( SELECT - build_number, - workspace_builds.job_id, + workspace_builds.build_number, workspace_builds.transition, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, provisioner_jobs.completed_at, - provisioner_jobs.error, - workspace_agents.created_at AS agent_created_at, - workspace_agents.disconnected_at AS agent_disconnected_at, - workspace_agents.first_connected_at AS agent_first_connected_at, - workspace_agents.last_connected_at AS agent_last_connected_at, - workspace_agents.connection_timeout_seconds AS agent_connection_timeout_seconds + provisioner_jobs.error FROM workspace_builds LEFT JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id - LEFT JOIN - workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id - LEFT JOIN - workspace_agents - ON - workspace_agents.resource_id = workspace_resources.id WHERE workspace_builds.workspace_id = workspaces.id ORDER BY - build_number DESC, - agent_last_connected_at ASC, - agent_first_connected_at ASC - LIMIT - 1 + build_number DESC + LIMIT 1 ) latest_build ON TRUE WHERE -- Optionally include deleted workspaces @@ -180,27 +205,15 @@ WHERE END -- Filter by agent status -- has-agent: is only applicable for workspaces in "start" transition. Stopped and deleted workspaces don't have agents. - -- The following CASE statement reflects the conditional logic in coderd/workspaceagents.go AND CASE WHEN @has_agent :: text != '' THEN - latest_build.transition = 'start'::workspace_transition - AND CASE - WHEN latest_build.agent_first_connected_at IS NULL THEN - CASE - WHEN latest_build.agent_connection_timeout_seconds > 0 AND NOW() - latest_build.agent_created_at > latest_build.agent_connection_timeout_seconds * INTERVAL '1 second' THEN - CASE WHEN @has_agent :: text = 'timeout' THEN true ELSE false END - ELSE - CASE WHEN @has_agent :: text = 'connecting' THEN true ELSE false END - END - WHEN latest_build.agent_disconnected_at > latest_build.agent_last_connected_at THEN - CASE WHEN @has_agent :: text = 'disconnected' THEN true ELSE false END - WHEN NOW() - latest_build.agent_last_connected_at > INTERVAL '1 second' * @agent_inactive_disconnect_timeout_seconds :: bigint THEN - CASE WHEN @has_agent :: text = 'disconnected' THEN true ELSE false END - WHEN latest_build.agent_last_connected_at IS NOT NULL THEN - CASE WHEN @has_agent :: text = 'connected' THEN true ELSE false END - ELSE - false - END + ( + SELECT COUNT(*) FROM workspace_builds_agents + WHERE + workspace_builds_agents.workspace_id = workspaces.id AND + workspace_builds_agents.build_number = latest_build.build_number AND + agent_status = @has_agent + ) > 0 ELSE true END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces From 9c2f64a062d54a560404c0537612edc81f0e4e76 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Wed, 23 Nov 2022 17:35:48 +0100 Subject: [PATCH 23/29] Polishing --- coderd/database/databasefake/databasefake.go | 6 +++--- coderd/database/queries.sql.go | 3 ++- coderd/database/queries/workspaces.sql | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 0c7e6beb93479..7416e408ca6a2 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -899,9 +899,9 @@ func (q *fakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. var hasAgentMatched bool for _, wa := range workspaceAgents { - mapped := mapAgentStatus(wa, arg.AgentInactiveDisconnectTimeoutSeconds) - hasAgentMatched = mapped == arg.HasAgent - break // only 1 agent is expected + if mapAgentStatus(wa, arg.AgentInactiveDisconnectTimeoutSeconds) == arg.HasAgent { + hasAgentMatched = true + } } if !hasAgentMatched { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 5c7bd7c4ddf02..97da563c80bee 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6401,7 +6401,8 @@ LEFT JOIN LATERAL ( workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC - LIMIT 1 + LIMIT + 1 ) latest_build ON TRUE WHERE -- Optionally include deleted workspaces diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index bbbfd31bc8b42..d016fb668adec 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -103,7 +103,8 @@ LEFT JOIN LATERAL ( workspace_builds.workspace_id = workspaces.id ORDER BY build_number DESC - LIMIT 1 + LIMIT + 1 ) latest_build ON TRUE WHERE -- Optionally include deleted workspaces From 44bf343d17d12b64600743c6720b832d67ba3c2f Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 11:32:41 +0100 Subject: [PATCH 24/29] Comment --- coderd/database/databasefake/databasefake.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 7416e408ca6a2..c678e9797f22b 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -947,6 +947,8 @@ 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 From 598b140fac6d3b76a81f43e4bea99898d84e397f Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 11:37:44 +0100 Subject: [PATCH 25/29] WIP --- coderd/database/queries/workspaces.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index d016fb668adec..36282b529f54f 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -64,15 +64,15 @@ WITH workspace_builds_agents AS ( ) AS agent_status FROM workspace_builds - LEFT JOIN + JOIN provisioner_jobs ON provisioner_jobs.id = workspace_builds.job_id - LEFT JOIN + JOIN workspace_resources ON workspace_resources.job_id = provisioner_jobs.id - LEFT JOIN + JOIN workspace_agents ON workspace_agents.resource_id = workspace_resources.id From ba13e03f1c5e3a859012b18f2c77a9af7136f657 Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 12:11:57 +0100 Subject: [PATCH 26/29] IS NOT NULL --- coderd/database/queries.sql.go | 3 +-- coderd/database/queries/workspaces.sql | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 31bcc6b848d60..e2763270c1f88 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6295,8 +6295,7 @@ WITH workspace_builds_agents AS ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.transition = 'start'::workspace_transition AND - workspace_agents.id IS NOT NULL + workspace_builds.transition = 'start'::workspace_transition ) SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, COUNT(*) OVER () as count diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 36282b529f54f..089c2fad3dc05 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -77,8 +77,7 @@ WITH workspace_builds_agents AS ( ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.transition = 'start'::workspace_transition AND - workspace_agents.id IS NOT NULL + workspace_builds.transition = 'start'::workspace_transition ) SELECT workspaces.*, COUNT(*) OVER () as count From d74b072b9fa12c89f8417cb4801011c2226367ab Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 14:03:31 +0100 Subject: [PATCH 27/29] Without CTE --- coderd/database/modelqueries.go | 2 +- coderd/database/queries.sql.go | 92 ++++++++++++-------------- coderd/database/queries/workspaces.sql | 82 +++++++++++------------ 3 files changed, 80 insertions(+), 96 deletions(-) diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 9f090509740c3..5b4fc7f5f3078 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -133,9 +133,9 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, + arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, - arg.AgentInactiveDisconnectTimeoutSeconds, ) if err != nil { return nil, xerrors.Errorf("get authorized workspaces: %w", err) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index e2763270c1f88..4d2740802eac7 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6256,47 +6256,6 @@ func (q *sqlQuerier) GetWorkspaceOwnerCountsByTemplateIDs(ctx context.Context, i } const getWorkspaces = `-- name: GetWorkspaces :many -WITH workspace_builds_agents AS ( - SELECT - workspace_builds.workspace_id AS workspace_id, - workspace_builds.build_number AS build_number, - workspace_agents.id AS agent_id, - ( - 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' * $11 :: bigint THEN - 'disconnected' - WHEN workspace_agents.last_connected_at IS NOT NULL THEN - 'connected' - ELSE - NULL - END - ) AS agent_status - FROM - workspace_builds - JOIN - provisioner_jobs - ON - provisioner_jobs.id = workspace_builds.job_id - JOIN - workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id - JOIN - workspace_agents - ON - workspace_agents.resource_id = workspace_resources.id - WHERE - workspace_builds.transition = 'start'::workspace_transition -) SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, COUNT(*) OVER () as count FROM @@ -6426,11 +6385,44 @@ WHERE AND CASE WHEN $8 :: text != '' THEN ( - SELECT COUNT(*) FROM workspace_builds_agents + SELECT COUNT(*) + FROM + workspace_builds + JOIN + provisioner_jobs + ON + provisioner_jobs.id = workspace_builds.job_id + JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds_agents.workspace_id = workspaces.id AND - workspace_builds_agents.build_number = latest_build.build_number AND - agent_status = $8 + workspace_builds.workspace_id = workspaces.id AND + workspace_builds.build_number = latest_build.build_number AND + workspace_builds.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 @@ -6440,11 +6432,11 @@ ORDER BY last_used_at DESC LIMIT CASE - WHEN $10 :: integer > 0 THEN - $10 + WHEN $11 :: integer > 0 THEN + $11 END OFFSET - $9 + $10 ` type GetWorkspacesParams struct { @@ -6456,9 +6448,9 @@ type GetWorkspacesParams struct { 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_"` - AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"` } type GetWorkspacesRow struct { @@ -6486,9 +6478,9 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) pq.Array(arg.TemplateIds), arg.Name, arg.HasAgent, + arg.AgentInactiveDisconnectTimeoutSeconds, arg.Offset, arg.Limit, - arg.AgentInactiveDisconnectTimeoutSeconds, ) if err != nil { return nil, err diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 089c2fad3dc05..d31d28e4307ac 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -38,47 +38,6 @@ WHERE ); -- name: GetWorkspaces :many -WITH workspace_builds_agents AS ( - SELECT - workspace_builds.workspace_id AS workspace_id, - workspace_builds.build_number AS build_number, - workspace_agents.id AS agent_id, - ( - 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 - ) AS agent_status - FROM - workspace_builds - JOIN - provisioner_jobs - ON - provisioner_jobs.id = workspace_builds.job_id - JOIN - workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id - JOIN - workspace_agents - ON - workspace_agents.resource_id = workspace_resources.id - WHERE - workspace_builds.transition = 'start'::workspace_transition -) SELECT workspaces.*, COUNT(*) OVER () as count FROM @@ -208,11 +167,44 @@ WHERE AND CASE WHEN @has_agent :: text != '' THEN ( - SELECT COUNT(*) FROM workspace_builds_agents + SELECT COUNT(*) + FROM + workspace_builds + JOIN + provisioner_jobs + ON + provisioner_jobs.id = workspace_builds.job_id + JOIN + workspace_resources + ON + workspace_resources.job_id = provisioner_jobs.id + JOIN + workspace_agents + ON + workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds_agents.workspace_id = workspaces.id AND - workspace_builds_agents.build_number = latest_build.build_number AND - agent_status = @has_agent + workspace_builds.workspace_id = workspaces.id AND + workspace_builds.build_number = latest_build.build_number AND + workspace_builds.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 From 425544858d4f33fdc4305178530b2c8eb9a5807c Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 15:00:24 +0100 Subject: [PATCH 28/29] One more optimization --- coderd/database/queries.sql.go | 11 +++-------- coderd/database/queries/workspaces.sql | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 4d2740802eac7..0147ed9837269 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6262,8 +6262,8 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT - workspace_builds.build_number, workspace_builds.transition, + provisioner_jobs.id AS provisioner_job_id, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, @@ -6387,11 +6387,7 @@ WHERE ( SELECT COUNT(*) FROM - workspace_builds - JOIN provisioner_jobs - ON - provisioner_jobs.id = workspace_builds.job_id JOIN workspace_resources ON @@ -6401,9 +6397,8 @@ WHERE ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspaces.id AND - workspace_builds.build_number = latest_build.build_number AND - workspace_builds.transition = 'start'::workspace_transition AND + provisioner_jobs.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 diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index d31d28e4307ac..c4900ea4a3db2 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -44,8 +44,8 @@ FROM workspaces LEFT JOIN LATERAL ( SELECT - workspace_builds.build_number, workspace_builds.transition, + provisioner_jobs.id AS provisioner_job_id, provisioner_jobs.started_at, provisioner_jobs.updated_at, provisioner_jobs.canceled_at, @@ -169,11 +169,7 @@ WHERE ( SELECT COUNT(*) FROM - workspace_builds - JOIN provisioner_jobs - ON - provisioner_jobs.id = workspace_builds.job_id JOIN workspace_resources ON @@ -183,9 +179,8 @@ WHERE ON workspace_agents.resource_id = workspace_resources.id WHERE - workspace_builds.workspace_id = workspaces.id AND - workspace_builds.build_number = latest_build.build_number AND - workspace_builds.transition = 'start'::workspace_transition AND + provisioner_jobs.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 From 4ace659f4a2a16bd9fe446eb2b9ac224681aaf2c Mon Sep 17 00:00:00 2001 From: Marcin Tojek Date: Thu, 24 Nov 2022 15:07:59 +0100 Subject: [PATCH 29/29] 2nd optimization --- coderd/database/queries.sql.go | 6 +----- coderd/database/queries/workspaces.sql | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 0147ed9837269..6cc3eb58bd234 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6387,17 +6387,13 @@ WHERE ( SELECT COUNT(*) FROM - provisioner_jobs - JOIN workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id JOIN workspace_agents ON workspace_agents.resource_id = workspace_resources.id WHERE - provisioner_jobs.id = latest_build.provisioner_job_id AND + workspace_resources.job_id = latest_build.provisioner_job_id AND latest_build.transition = 'start'::workspace_transition AND $8 = ( CASE diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index c4900ea4a3db2..08fc3c4dbf673 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -169,17 +169,13 @@ WHERE ( SELECT COUNT(*) FROM - provisioner_jobs - JOIN workspace_resources - ON - workspace_resources.job_id = provisioner_jobs.id JOIN workspace_agents ON workspace_agents.resource_id = workspace_resources.id WHERE - provisioner_jobs.id = latest_build.provisioner_job_id AND + workspace_resources.job_id = latest_build.provisioner_job_id AND latest_build.transition = 'start'::workspace_transition AND @has_agent = ( CASE