Skip to content

Commit c09d750

Browse files
committed
implement proposal, add tests and examples
1 parent 15cfc3f commit c09d750

14 files changed

+285
-71
lines changed

cli/testdata/coder_list_--output_json.golden

+1-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@
5555
"locked_at": null,
5656
"health": {
5757
"healthy": true,
58-
"failing_sections": [],
59-
"agents": {}
58+
"failing_agents": []
6059
}
6160
}
6261
]

coderd/apidoc/docs.go

+30-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+30-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/workspaces.go

+13
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,15 @@ func convertWorkspace(
11101110
lockedAt = &workspace.LockedAt.Time
11111111
}
11121112

1113+
failingAgents := []uuid.UUID{}
1114+
for _, resource := range workspaceBuild.Resources {
1115+
for _, agent := range resource.Agents {
1116+
if !agent.Health.Healthy {
1117+
failingAgents = append(failingAgents, agent.ID)
1118+
}
1119+
}
1120+
}
1121+
11131122
var (
11141123
ttlMillis = convertWorkspaceTTLMillis(workspace.Ttl)
11151124
deletingAt = calculateDeletingAt(workspace, template, workspaceBuild)
@@ -1135,6 +1144,10 @@ func convertWorkspace(
11351144
LastUsedAt: workspace.LastUsedAt,
11361145
DeletingAt: deletingAt,
11371146
LockedAt: lockedAt,
1147+
Health: codersdk.WorkspaceHealth{
1148+
Healthy: len(failingAgents) == 0,
1149+
FailingAgents: failingAgents,
1150+
},
11381151
}
11391152
}
11401153

coderd/workspaces_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,99 @@ func TestWorkspace(t *testing.T) {
165165
assert.Equal(t, templateDisplayName, ws.TemplateDisplayName)
166166
assert.Equal(t, templateAllowUserCancelWorkspaceJobs, ws.TemplateAllowUserCancelWorkspaceJobs)
167167
})
168+
169+
t.Run("Health", func(t *testing.T) {
170+
t.Parallel()
171+
172+
t.Run("Healthy", func(t *testing.T) {
173+
t.Parallel()
174+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
175+
user := coderdtest.CreateFirstUser(t, client)
176+
authToken := uuid.NewString()
177+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
178+
Parse: echo.ParseComplete,
179+
ProvisionApply: []*proto.Provision_Response{{
180+
Type: &proto.Provision_Response_Complete{
181+
Complete: &proto.Provision_Complete{
182+
Resources: []*proto.Resource{{
183+
Name: "some",
184+
Type: "example",
185+
Agents: []*proto.Agent{{
186+
Id: uuid.NewString(),
187+
Auth: &proto.Agent_Token{
188+
Token: authToken,
189+
},
190+
ConnectionTimeoutSeconds: 10,
191+
}},
192+
}},
193+
},
194+
},
195+
}},
196+
})
197+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
198+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
199+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
200+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
201+
202+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
203+
defer cancel()
204+
205+
workspace, err := client.Workspace(ctx, workspace.ID)
206+
require.NoError(t, err)
207+
agent := workspace.LatestBuild.Resources[0].Agents[0]
208+
209+
assert.True(t, agent.Health.Healthy)
210+
assert.Empty(t, agent.Health.Reason)
211+
})
212+
213+
t.Run("Unhealthy", func(t *testing.T) {
214+
t.Parallel()
215+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
216+
user := coderdtest.CreateFirstUser(t, client)
217+
authToken := uuid.NewString()
218+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
219+
Parse: echo.ParseComplete,
220+
ProvisionApply: []*proto.Provision_Response{{
221+
Type: &proto.Provision_Response_Complete{
222+
Complete: &proto.Provision_Complete{
223+
Resources: []*proto.Resource{{
224+
Name: "some",
225+
Type: "example",
226+
Agents: []*proto.Agent{{
227+
Id: uuid.NewString(),
228+
Auth: &proto.Agent_Token{
229+
Token: authToken,
230+
},
231+
ConnectionTimeoutSeconds: 1,
232+
}},
233+
}},
234+
},
235+
},
236+
}},
237+
})
238+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
239+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
240+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
241+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
242+
243+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
244+
defer cancel()
245+
246+
var err error
247+
testutil.Eventually(ctx, t, func(ctx context.Context) bool {
248+
workspace, err = client.Workspace(ctx, workspace.ID)
249+
t.Logf("%#v", workspace)
250+
return assert.NoError(t, err) && !workspace.Health.Healthy
251+
}, testutil.IntervalMedium)
252+
253+
agent := workspace.LatestBuild.Resources[0].Agents[0]
254+
255+
assert.False(t, workspace.Health.Healthy)
256+
assert.Len(t, workspace.Health.FailingAgents, 1)
257+
assert.False(t, agent.Health.Healthy)
258+
assert.NotEmpty(t, agent.Health.Reason)
259+
})
260+
})
168261
}
169262

170263
func TestAdminViewAllWorkspaces(t *testing.T) {

codersdk/workspaceagents.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ type WorkspaceAgent struct {
172172
}
173173

174174
type WorkspaceAgentHealth struct {
175-
Healthy bool `json:"healthy"` // Healthy is true if the agent is healthy.
176-
Reason string `json:"reason,omitempty"` // Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true.
175+
Healthy bool `json:"healthy" example:"false"` // Healthy is true if the agent is healthy.
176+
Reason string `json:"reason,omitempty" example:"agent has lost connection"` // Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true.
177177
}
178178

179179
type DERPRegion struct {

codersdk/workspaces.go

+8
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ type Workspace struct {
4343
// unlocked by an admin. It is subject to deletion if it breaches
4444
// the duration of the locked_ttl field on its template.
4545
LockedAt *time.Time `json:"locked_at" format:"date-time"`
46+
// Health shows the health of the workspace and information about
47+
// what is causing an unhealthy status.
48+
Health WorkspaceHealth `json:"health"`
49+
}
50+
51+
type WorkspaceHealth struct {
52+
Healthy bool `json:"healthy" example:"false"` // Healthy is true if the workspace is healthy.
53+
FailingAgents []uuid.UUID `json:"failing_agents" format:"uuid"` // FailingAgents lists the IDs of the agents that are failing, if any.
4654
}
4755

4856
type WorkspacesRequest struct {

docs/api/agents.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
451451
"expanded_directory": "string",
452452
"first_connected_at": "2019-08-24T14:15:22Z",
453453
"health": {
454-
"healthy": true,
455-
"reason": "string"
454+
"healthy": false,
455+
"reason": "agent has lost connection"
456456
},
457457
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
458458
"instance_id": "string",

docs/api/builds.md

+12-12
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
8989
"expanded_directory": "string",
9090
"first_connected_at": "2019-08-24T14:15:22Z",
9191
"health": {
92-
"healthy": true,
93-
"reason": "string"
92+
"healthy": false,
93+
"reason": "agent has lost connection"
9494
},
9595
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
9696
"instance_id": "string",
@@ -251,8 +251,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
251251
"expanded_directory": "string",
252252
"first_connected_at": "2019-08-24T14:15:22Z",
253253
"health": {
254-
"healthy": true,
255-
"reason": "string"
254+
"healthy": false,
255+
"reason": "agent has lost connection"
256256
},
257257
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
258258
"instance_id": "string",
@@ -552,8 +552,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res
552552
"expanded_directory": "string",
553553
"first_connected_at": "2019-08-24T14:15:22Z",
554554
"health": {
555-
"healthy": true,
556-
"reason": "string"
555+
"healthy": false,
556+
"reason": "agent has lost connection"
557557
},
558558
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
559559
"instance_id": "string",
@@ -810,8 +810,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
810810
"expanded_directory": "string",
811811
"first_connected_at": "2019-08-24T14:15:22Z",
812812
"health": {
813-
"healthy": true,
814-
"reason": "string"
813+
"healthy": false,
814+
"reason": "agent has lost connection"
815815
},
816816
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
817817
"instance_id": "string",
@@ -977,8 +977,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
977977
"expanded_directory": "string",
978978
"first_connected_at": "2019-08-24T14:15:22Z",
979979
"health": {
980-
"healthy": true,
981-
"reason": "string"
980+
"healthy": false,
981+
"reason": "agent has lost connection"
982982
},
983983
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
984984
"instance_id": "string",
@@ -1325,8 +1325,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
13251325
"expanded_directory": "string",
13261326
"first_connected_at": "2019-08-24T14:15:22Z",
13271327
"health": {
1328-
"healthy": true,
1329-
"reason": "string"
1328+
"healthy": false,
1329+
"reason": "agent has lost connection"
13301330
},
13311331
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
13321332
"instance_id": "string",

0 commit comments

Comments
 (0)