10000 Improve workspace history logs · coder/coder@c50cee0 · GitHub
[go: up one dir, main page]

Skip to content

Commit c50cee0

Browse files
committed
Improve workspace history logs
1 parent 0fb2a5a commit c50cee0

File tree

2 files changed

+109
-72
lines changed

2 files changed

+109
-72
lines changed

coderd/workspacehistorylogs.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,16 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
8787
})
8888
if errors.Is(err, sql.ErrNoRows) {
8989
err = nil
90-
logs = []database.WorkspaceHistoryLog{}
9190
}
9291
if err != nil {
9392
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
9493
Message: fmt.Sprintf("get workspace history: %s", err),
9594
})
9695
return
9796
}
97+
if logs == nil {
98+
logs = []database.WorkspaceHistoryLog{}
99+
}
98100
render.Status(r, http.StatusOK)
99101
render.JSON(rw, r, logs)
100102
return
@@ -113,12 +115,8 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
113115
select {
114116
case bufferedLogs <- log:
115117
default:
116-
// This is a case that shouldn't happen, but totally could.
117-
// There's no way to stream data from the database, so we'll
118-
// need to maintain some level of internal buffer.
119-
//
120-
// If this overflows users could miss logs when streaming.
121-
// We warn to make sure we know when it happens!
118+
// If this overflows users could miss logs streaming. This can happen
119+
// if a database request takes a long amount of time, and we get a lot of logs.
122120
api.Logger.Warn(r.Context(), "workspace history log overflowing channel")
123121
}
124122
}

coderd/workspacehistorylogs_test.go

Lines changed: 104 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,93 +9,132 @@ import (
99

1010
"github.com/coder/coder/coderd"
1111
"github.com/coder/coder/coderd/coderdtest"
12-
"github.com/coder/coder/codersdk"
1312
"github.com/coder/coder/database"
1413
"github.com/coder/coder/provisioner/echo"
1514
"github.com/coder/coder/provisionersdk/proto"
1615
)
1716

18-
func TestWorkspaceHistoryLogs(t *testing.T) {
17+
func TestWorkspaceHistoryLogsByName(t *testing.T) {
1918
t.Parallel()
20-
21-
setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) {
22-
project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
23-
Name: "banana",
24-
Provisioner: database.ProvisionerTypeEcho,
19+
t.Run("List", func(t *testing.T) {
20+
t.Parallel()
21+
client := coderdtest.New(t)
22+
user := coderdtest.CreateInitialUser(t, client)
23+
coderdtest.NewProvisionerDaemon(t, client)
24+
project := coderdtest.CreateProject(t, client, user.Organization)
25+
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
26+
Parse: echo.ParseComplete,
27+
Provision: []*proto.Provision_Response{{
28+
Type: &proto.Provision_Response_Log{
29+
Log: &proto.Log{
30+
Level: proto.LogLevel_INFO,
31+
Output: "log-output",
32+
},
33+
},
34+
}, {
35+
Type: &proto.Provision_Response_Complete{
36+
Complete: &proto.Provision_Complete{},
37+
},
38+
}},
2539
})
26-
require.NoError(t, err)
27-
workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
28-
Name: "example",
29-
ProjectID: project.ID,
40+
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
41+
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
42+
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
43+
ProjectVersionID: version.ID,
44+
Transition: database.WorkspaceTransitionCreate,
3045
})
3146
require.NoError(t, err)
32-
return project, workspace
33-
}
3447

35-
setupProjectVersion := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectVersion {
36-
projectVersion, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
37-
StorageMethod: database.ProjectStorageMethodInlineArchive,
38-
StorageSource: data,
39-
})
48+
// Successfully return empty logs before the job starts!
49+
logs, err := client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
4050
require.NoError(t, err)
41-
require.Eventually(t, func() bool {
42-
hist, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, projectVersion.Name)
43-
require.NoError(t, err)
44-
return hist.Import.Status.Completed()
45-
}, 15*time.Second, 50*time.Millisecond)
46-
return projectVersion
47-
}
51+
require.NotNil(t, logs)
52+
require.Len(t, logs, 0)
4853

49-
client := coderdtest.New(t)
50-
user := coderdtest.CreateInitialUser(t, client)
51-
_ = coderdtest.NewProvisionerDaemon(t, client)
52-
project, workspace := setupProjectAndWorkspace(t, client, user)
53-
data, err := echo.Tar(&echo.Responses{
54-
Parse: echo.ParseComplete,
55-
Provision: []*proto.Provision_Response{{
56-
Type: &proto.Provision_Response_Log{
57-
Log: &proto.Log{
58-
Output: "test",
59-
},
60-
},
61-
}, {
62-
Type: &proto.Provision_Response_Complete{
63-
Complete: &proto.Provision_Complete{},
64-
},
65-
}},
66-
})
67-
require.NoError(t, err)
68-
projectVersion := setupProjectVersion(t, client, user, project, data)
54+
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
6955

70-
workspaceHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
71-
ProjectVersionID: projectVersion.ID,
72-
Transition: database.WorkspaceTransitionCreate,
56+
// Return the log after completion!
57+
logs, err = client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
58+
require.NoError(t, err)
59+
require.NotNil(t, logs)
60+
require.Len(t, logs, 1)
7361
})
74-
require.NoError(t, err)
75-
76-
now := database.Now()
77-
logChan, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, workspaceHistory.Name, now)
78-
require.NoError(t, err)
79-
80-
for {
81-
log, more := <-logChan
82-
if !more {
83-
break
84-
}
85-
t.Logf("Output: %s", log.Output)
86-
}
8762

88-
t.Run("ReturnAll", func(t *testing.T) {
63+
t.Run("StreamAfterComplete", func(t *testing.T) {
8964
t.Parallel()
65+
client := coderdtest.New(t)
66+
user := coderdtest.CreateInitialUser(t, client)
67+
coderdtest.NewProvisionerDaemon(t, client)
68+
project := coderdtest.CreateProject(t, client, user.Organization)
69+
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
70+
Parse: echo.ParseComplete,
71+
Provision: []*proto.Provision_Response{{
72+
Type: &proto.Provision_Response_Log{
73+
Log: &proto.Log{
74+
Level: proto.LogLevel_INFO,
75+
Output: "log-output",
76+
},
77+
},
78+
}, {
79+
Type: &proto.Provision_Response_Complete{
80+
Complete: &proto.Provision_Complete{},
81+
},
82+
}},
83+
})
84+
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
85+
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
86+
before := time.Now().UTC()
87+
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
88+
ProjectVersionID: version.ID,
89+
Transition: database.WorkspaceTransitionCreate,
90+
})
91+
require.NoError(t, err)
92+
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
9093

91-
_, err := client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, workspaceHistory.Name)
94+
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, before)
9295
require.NoError(t, err)
96+
log := <-logs
97+
require.Equal(t, "log-output", log.Output)
98+
// Make sure the channel automatically closes!
99+
_, ok := <-logs
100+
require.False(t, ok)
93101
})
94102

95-
t.Run("Between", func(t *testing.T) {
103+
t.Run("StreamWhileRunning", func(t *testing.T) {
96104
t.Parallel()
105+
client := coderdtest.New(t)
106+
user := coderdtest.CreateInitialUser(t, client)
107+
coderdtest.NewProvisionerDaemon(t, client)
108+
project := coderdtest.CreateProject(t, client, user.Organization)
109+
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
110+
Parse: echo.ParseComplete,
111+
Provision: []*proto.Provision_Response{{
112+
Type: &proto.Provision_Response_Log{
113+
Log: &proto.Log{
114+
Level: proto.LogLevel_INFO,
115+
Output: "log-output",
116+
},
117+
},
118+
}, {
119+
Type: &proto.Provision_Response_Complete{
120+
Complete: &proto.Provision_Complete{},
121+
},
122+
}},
123+
})
124+
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
125+
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
126+
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
127+
ProjectVersionID: version.ID,
128+
Transition: database.WorkspaceTransitionCreate,
129+
})
130+
require.NoError(t, err)
97131

98-
_, err := client.WorkspaceHistoryLogsBetween(context.Background(), "", workspace.Name, workspaceHistory.Name, time.Time{}, database.Now())
132+
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, time.Time{})
99133
require.NoError(t, err)
134+
log := <-logs
135+
require.Equal(t, "log-output", log.Output)
136+
// Make sure the channel automatically closes!
137+
_, ok := <-logs
138+
require.False(t, ok)
100139
})
101140
}

0 commit comments

Comments
 (0)
0