From 752bcdc8b394b5c78344ce0e96deee0017b70aff Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 28 Aug 2023 14:17:00 +0000 Subject: [PATCH 1/2] fix(coderd): allow `workspaceAgentLogs` follow to return on non-latest-build --- coderd/workspaceagents.go | 25 ++++++++- coderd/workspaceagents_test.go | 98 ++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index c127b2342d4a3..fee75df08604b 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -23,6 +23,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" "golang.org/x/exp/maps" + "golang.org/x/exp/slices" "golang.org/x/mod/semver" "golang.org/x/sync/errgroup" "golang.org/x/xerrors" @@ -481,6 +482,15 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { return } + workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace by agent id.", + Detail: err.Error(), + }) + return + } + api.WebsocketWaitMutex.Lock() api.WebsocketWaitGroup.Add(1) api.WebsocketWaitMutex.Unlock() @@ -556,7 +566,8 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { go func() { defer close(bufferedLogs) - for { + keepGoing := true + for keepGoing { select { case <-ctx.Done(): return @@ -565,6 +576,18 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { t.Reset(recheckInterval) } + agents, err := api.Database.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx, workspace.ID) + if err != nil { + if xerrors.Is(err, context.Canceled) { + return + } + logger.Warn(ctx, "failed to get workspace agents in latest build", slog.Error(err)) + continue + } + // If the agent is no longer in the latest build, we can stop after + // checking once. + keepGoing = slices.ContainsFunc(agents, func(agent database.WorkspaceAgent) bool { return agent.ID == workspaceAgent.ID }) + logs, err := api.Database.GetWorkspaceAgentLogsAfter(ctx, database.GetWorkspaceAgentLogsAfterParams{ AgentID: workspaceAgent.ID, CreatedAfter: lastSentLogID, diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 43694e95e67a9..54b74947746bc 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -242,6 +242,104 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { require.Equal(t, "testing", logChunk[0].Output) require.Equal(t, "testing2", logChunk[1].Output) }) + t.Run("Close logs on outdated build", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) + 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.PlanComplete, + ProvisionApply: []*proto.Response{{ + Type: &proto.Response_Apply{ + Apply: &proto.ApplyComplete{ + 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) + build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + agentClient := agentsdk.New(client.URL) + agentClient.SetSessionToken(authToken) + err := agentClient.PatchLogs(ctx, agentsdk.PatchLogs{ + Logs: []agentsdk.Log{ + { + CreatedAt: database.Now(), + Output: "testing", + }, + }, + }) + require.NoError(t, err) + + logs, closer, err := client.WorkspaceAgentLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true) + require.NoError(t, err) + defer func() { + _ = closer.Close() + }() + + first := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + assert.Fail(t, "context done while waiting in goroutine") + case <-logs: + close(first) + } + }() + select { + case <-ctx.Done(): + require.FailNow(t, "context done while waiting for first log") + case <-first: + } + + // Create a new version and update the template. + version2, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{ + TemplateID: template.ID, + Name: "version2", + StorageMethod: codersdk.ProvisionerStorageMethodFile, + FileID: version.Job.FileID, + Provisioner: codersdk.ProvisionerTypeEcho, + }) + require.NoError(t, err) + coderdtest.AwaitTemplateVersionJob(t, client, version2.ID) + + _ = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart, func(req *codersdk.CreateWorkspaceBuildRequest) { + req.TemplateVersionID = version2.ID + }) + + // Send a new log message to trigger a re-check. + err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{ + Logs: []agentsdk.Log{ + { + CreatedAt: database.Now(), + Output: "testing2", + }, + }, + }) + require.NoError(t, err) + + select { + case <-ctx.Done(): + require.FailNow(t, "context done while waiting for logs close") + case <-logs: + } + }) t.Run("PublishesOnOverflow", func(t *testing.T) { t.Parallel() ctx := testutil.Context(t, testutil.WaitMedium) From 671e482e5ac5a02587e4a0f875bccd2d6262a9ed Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 28 Aug 2023 14:26:55 +0000 Subject: [PATCH 2/2] skip the tpl version switch --- coderd/workspaceagents_test.go | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 54b74947746bc..2e51687afafa6 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -308,20 +308,7 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { case <-first: } - // Create a new version and update the template. - version2, err := client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{ - TemplateID: template.ID, - Name: "version2", - StorageMethod: codersdk.ProvisionerStorageMethodFile, - FileID: version.Job.FileID, - Provisioner: codersdk.ProvisionerTypeEcho, - }) - require.NoError(t, err) - coderdtest.AwaitTemplateVersionJob(t, client, version2.ID) - - _ = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart, func(req *codersdk.CreateWorkspaceBuildRequest) { - req.TemplateVersionID = version2.ID - }) + _ = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart) // Send a new log message to trigger a re-check. err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{