@@ -1097,6 +1097,12 @@ func TestPostWorkspaceBuild(t *testing.T) {
1097
1097
Transition :codersdk .WorkspaceTransitionStart ,
1098
1098
})
1099
1099
require .NoError (t ,err )
1100
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1101
+ require .Equal (t ,1 ,build .MatchedProvisioners .Count )
1102
+ require .Equal (t ,1 ,build .MatchedProvisioners .Available )
1103
+ require .NotZero (t ,build .MatchedProvisioners .MostRecentlySeen .Time )
1104
+ }
1105
+
1100
1106
coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,build .ID )
1101
1107
1102
1108
require .Eventually (t ,func ()bool {
@@ -1124,6 +1130,12 @@ func TestPostWorkspaceBuild(t *testing.T) {
1124
1130
Transition :codersdk .WorkspaceTransitionStart ,
1125
1131
})
1126
1132
require .NoError (t ,err )
1133
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1134
+ require .Equal (t ,1 ,build .MatchedProvisioners .Count )
1135
+ require .Equal (t ,1 ,build .MatchedProvisioners .Available )
1136
+ require .NotZero (t ,build .MatchedProvisioners .MostRecentlySeen .Time )
1137
+ }
1138
+
1127
1139
require .Equal (t ,workspace .LatestBuild .BuildNumber + 1 ,build .BuildNumber )
1128
1140
})
1129
1141
@@ -1150,6 +1162,12 @@ func TestPostWorkspaceBuild(t *testing.T) {
1150
1162
ProvisionerState :wantState ,
1151
1163
})
1152
1164
require .NoError (t ,err )
1165
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1166
+ require .Equal (t ,1 ,build .MatchedProvisioners .Count )
1167
+ require .Equal (t ,1 ,build .MatchedProvisioners .Available )
1168
+ require .NotZero (t ,build .MatchedProvisioners .MostRecentlySeen .Time )
1169
+ }
1170
+
1153
1171
gotState ,err := client .WorkspaceBuildState (ctx ,build .ID )
1154
1172
require .NoError (t ,err )
1155
1173
require .Equal (t ,wantState ,gotState )
@@ -1173,6 +1191,12 @@ func TestPostWorkspaceBuild(t *testing.T) {
1173
1191
})
1174
1192
require .NoError (t ,err )
1175
1193
require .Equal (t ,workspace .LatestBuild .BuildNumber + 1 ,build .BuildNumber )
1194
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1195
+ require .Equal (t ,1 ,build .MatchedProvisioners .Count )
1196
+ require .Equal (t ,1 ,build .MatchedProvisioners .Available )
1197
+ require .NotZero (t ,build .MatchedProvisioners .MostRecentlySeen .Time )
1198
+ }
1199
+
1176
1200
coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,build .ID )
1177
1201
1178
1202
res ,err := client .Workspaces (ctx , codersdk.WorkspaceFilter {
@@ -1181,6 +1205,92 @@ func TestPostWorkspaceBuild(t *testing.T) {
1181
1205
require .NoError (t ,err )
1182
1206
require .Len (t ,res .Workspaces ,0 )
1183
1207
})
1208
+
1209
+ t .Run ("NoProvisionersAvailable" ,func (t * testing.T ) {
1210
+ t .Parallel ()
1211
+ // Given: a coderd instance with a provisioner daemon
1212
+ store ,ps ,db := dbtestutil .NewDBWithSQLDB (t )
1213
+ client ,closeDaemon := coderdtest .NewWithProvisionerCloser (t ,& coderdtest.Options {
1214
+ Database :store ,
1215
+ Pubsub :ps ,
1216
+ IncludeProvisionerDaemon :true ,
1217
+ })
1218
+ // Given: a user, template, and workspace
1219
+ user := coderdtest .CreateFirstUser (t ,client )
1220
+ version := coderdtest .CreateTemplateVersion (t ,client ,user .OrganizationID ,nil )
1221
+ coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
1222
+ template := coderdtest .CreateTemplate (t ,client ,user .OrganizationID ,version .ID )
1223
+ workspace := coderdtest .CreateWorkspace (t ,client ,template .ID )
1224
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,workspace .LatestBuild .ID )
1225
+
1226
+ // Stop the provisioner daemon.
1227
+ require .NoError (t ,closeDaemon .Close ())
1228
+ ctx := testutil .Context (t ,testutil .WaitLong )
1229
+ // Given: no provisioner daemons exist.
1230
+ _ ,err := db .ExecContext (ctx ,`DELETE FROM provisioner_daemons;` )
1231
+ require .NoError (t ,err )
1232
+
1233
+ // When: a new workspace build is created
1234
+ build ,err := client .CreateWorkspaceBuild (ctx ,workspace .ID , codersdk.CreateWorkspaceBuildRequest {
1235
+ TemplateVersionID :template .ActiveVersionID ,
1236
+ Transition :codersdk .WorkspaceTransitionStart ,
1237
+ })
1238
+ // Then: the request should succeed.
1239
+ require .NoError (t ,err )
1240
+ // Then: the provisioner job should remain pending.
1241
+ require .Equal (t ,codersdk .ProvisionerJobPending ,build .Job .Status )
1242
+ // Then: the response should indicate no provisioners are available.
1243
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1244
+ require .Zero (t ,build .MatchedProvisioners .Count )
1245
+ require .Zero (t ,build .MatchedProvisioners .Available )
1246
+ require .Zero (t ,build .MatchedProvisioners .MostRecentlySeen .Time )
1247
+ }
1248
+ })
1249
+
1250
+ t .Run ("AllProvisionersStale" ,func (t * testing.T ) {
1251
+ t .Parallel ()
1252
+ // Given: a coderd instance with a provisioner daemon
1253
+ store ,ps ,db := dbtestutil .NewDBWithSQLDB (t )
1254
+ client ,closeDaemon := coderdtest .NewWithProvisionerCloser (t ,& coderdtest.Options {
1255
+ Database :store ,
1256
+ Pubsub :ps ,
1257
+ IncludeProvisionerDaemon :true ,
1258
+ })
1259
+ // Given: a user, template, and workspace
1260
+ user := coderdtest .CreateFirstUser (t ,client )
1261
+ version := coderdtest .CreateTemplateVersion (t ,client ,user .OrganizationID ,nil )
1262
+ coderdtest .AwaitTemplateVersionJobCompleted (t ,client ,version .ID )
1263
+ template := coderdtest .CreateTemplate (t ,client ,user .OrganizationID ,version .ID )
1264
+ workspace := coderdtest .CreateWorkspace (t ,client ,template .ID )
1265
+ coderdtest .AwaitWorkspaceBuildJobCompleted (t ,client ,workspace .LatestBuild .ID )
1266
+
1267
+ ctx := testutil .Context (t ,testutil .WaitLong )
1268
+ // Given: all provisioner daemons are stale
1269
+ // First stop the provisioner
1270
+ require .NoError (t ,closeDaemon .Close ())
1271
+ newLastSeenAt := dbtime .Now ().Add (- time .Hour )
1272
+ // Update the last seen at for all provisioner daemons. We have to use the
1273
+ // SQL db directly because store.UpdateProvisionerDaemonLastSeenAt has a
1274
+ // built-in check to prevent updating the last seen at to a time in the past.
1275
+ _ ,err := db .ExecContext (ctx ,`UPDATE provisioner_daemons SET last_seen_at = $1;` ,newLastSeenAt )
1276
+ require .NoError (t ,err )
1277
+
1278
+ // When: a new workspace build is created
1279
+ build ,err := client .CreateWorkspaceBuild (ctx ,workspace .ID , codersdk.CreateWorkspaceBuildRequest {
1280
+ TemplateVersionID :template .ActiveVersionID ,
1281
+ Transition :codersdk .WorkspaceTransitionStart ,
1282
+ })
1283
+ // Then: the request should succeed
1284
+ require .NoError (t ,err )
1285
+ // Then: the provisioner job should remain pending
1286
+ require .Equal (t ,codersdk .ProvisionerJobPending ,build .Job .Status )
1287
+ // Then: the response should indicate no provisioners are available
1288
+ if assert .NotNil (t ,build .MatchedProvisioners ) {
1289
+ require .Zero (t ,build .MatchedProvisioners .Available )
1290
+ require .Equal (t ,1 ,build .MatchedProvisioners .Count )
1291
+ require .Equal (t ,newLastSeenAt .UTC (),build .MatchedProvisioners .MostRecentlySeen .Time .UTC ())
1292
+ }
1293
+ })
1184
1294
}
1185
1295
1186
1296
func TestWorkspaceBuildTimings (t * testing.T ) {