@@ -8,6 +8,7 @@ const docker = require('./docker');
8
8
const log = require ( './log' ) ;
9
9
const metrics = require ( './metrics' ) ;
10
10
const streams = require ( './streams' ) ;
11
+ const tasks = require ( './tasks' ) ;
11
12
12
13
// List available user machines for each project, create when necessary.
13
14
exports . getAvailableMachines = function ( user ) {
@@ -195,6 +196,56 @@ exports.spawn = function (user, projectId, callback) {
195
196
196
197
const machine = getOrCreateNewMachine ( user , projectId ) ;
197
198
199
+ _spawn ( machine , project , function ( error , machine ) {
200
+ if ( error ) {
201
+ callback ( error ) ;
202
+ return ;
203
+ }
204
+
205
+ const containerId = machine . docker . container ;
206
+ // Quickly authorize the user's public SSH keys to access this container.
207
+ deploySSHAuthorizedKeys ( user , machine , error => {
208
+ log ( 'spawn-sshkeys' , containerId . slice ( 0 , 16 ) , error || 'success' ) ;
209
+ db . save ( ) ;
210
+ } ) ;
211
+
212
+ // Install all non-empty user configuration files into this container.
213
+ Object . keys ( user . configurations ) . forEach ( file => {
214
+ if ( ! user . configurations [ file ] ) {
215
+ return ;
216
+ }
217
+ exports . deployConfiguration ( user , machine , file ) . then ( ( ) => {
218
+ log ( 'spawn-config' , file , containerId . slice ( 0 , 16 ) , 'success' ) ;
219
+ } ) . catch ( error => {
220
+ log ( 'spawn-config' , file , containerId . slice ( 0 , 16 ) , error ) ;
221
+ } ) ;
222
+ } ) ;
223
+ callback ( null , machine ) ;
224
+ } ) ;
225
+ } ;
226
+
227
+ // Instantiate a new temporary machine for a project. (Fast!)
228
+ exports . spawnTemporary = function ( sessionId , projectId , callback ) {
229
+ const machine = createNewTemporaryMachine ( sessionId , projectId ) ;
230
+
231
+ _spawn ( machine , getProject ( projectId ) , ( error , machine ) => {
232
+ if ( error ) {
233
+ callback ( error ) ;
234
+ return ;
235
+ }
236
+
237
+ const destroyDate = new Date ( Date . now ( ) ) ;
238
+ destroyDate . setHours ( destroyDate . getHours ( ) + 8 ) ;
239
+ tasks . add ( destroyDate , 'destroy-temporary' , {
240
+ session : sessionId ,
241
+ container : machine . docker . container
242
+ } ) ;
243
+
244
+ callback ( null , machine ) ;
245
+ } ) ;
246
+ } ;
247
+
248
+ function _spawn ( machine , project , callback ) {
198
249
// Keep track of the last project update this machine will be based on.
199
250
metrics . set ( machine , 'updated' , project . data . updated ) ;
200
251
@@ -217,7 +268,7 @@ exports.spawn = function (user, projectId, callback) {
217
268
log ( 'spawn' , image , error ) ;
218
269
machine . status = 'start-failed' ;
219
270
db . save ( ) ;
220
- callback ( new Error ( 'Unable to start machine for project: ' + projectId ) ) ;
271
+ callback ( new Error ( 'Unable to start machine for project: ' + project . id ) ) ;
221
272
return ;
222
273
}
223
274
@@ -230,27 +281,9 @@ exports.spawn = function (user, projectId, callback) {
230
281
metrics . push ( project , 'spawn-time' , [ now , now - time ] ) ;
231
282
db . save ( ) ;
232
283
233
- // Quickly authorize the user's public SSH keys to access this container.
234
- deploySSHAuthorizedKeys ( user , machine , error => {
235
- log ( 'spawn-sshkeys' , container . id . slice ( 0 , 16 ) , error || 'success' ) ;
236
- db . save ( ) ;
237
- } ) ;
238
-
239
- // Install all non-empty user configuration files into this container.
240
- Object . keys ( user . configurations ) . forEach ( file => {
241
- if ( ! user . configurations [ file ] ) {
242
- return ;
243
- }
244
- exports . deployConfiguration ( user , machine , file ) . then ( ( ) => {
245
- log ( 'spawn-config' , file , container . id . slice ( 0 , 16 ) , 'success' ) ;
246
- } ) . catch ( error => {
247
- log ( 'spawn-config' , file , container . id . slice ( 0 , 16 ) , error ) ;
248
- } ) ;
249
- } ) ;
250
-
251
284
callback ( null , machine ) ;
252
285
} ) ;
253
- } ;
286
+ }
254
287
255
288
// Destroy a given user machine and recycle its ports.
256
289
exports . destroy = function ( user , projectId , machineId , callback ) {
@@ -267,7 +300,7 @@ exports.destroy = function (user, projectId, machineId, callback) {
267
300
return ;
268
301
}
269
302
270
- const { container : containerId , host } = machine . docker ;
303
+ const containerId = machine . docker . container ;
271
304
if ( ! containerId ) {
272
305
// This machine has no associated container, just recycle it as is.
273
306
machine . status = 'new' ;
@@ -276,19 +309,55 @@ exports.destroy = function (user, projectId, machineId, callback) {
276
309
return ;
277
310
}
278
311
279
- log ( 'destroy' , containerId . slice ( 0 , 16 ) , 'started' ) ;
280
- docker . removeContainer ( { host, container : containerId } , error => {
312
+ _destroy ( machine , ( error ) => {
281
313
if ( error ) {
282
- log ( 'destroy' , containerId . slice ( 0 , 16 ) , error ) ;
283
314
callback ( error ) ;
284
- return ;
285
315
}
286
316
287
317
// Recycle the machine's name and ports.
288
318
machine . status = 'new' ;
289
319
machine . docker . container = '' ;
290
320
db . save ( ) ;
291
321
callback ( ) ;
322
+ } ) ;
323
+ } ;
324
+
325
+ exports . destroyTemporary = function ( sessionId , containerId , callback ) {
326
+ const machines = db . get ( 'temporaryMachines' ) [ sessionId ] ;
327
+ if ( ! machines ) {
328
+ callback ( new Error ( 'No machines for session ' + sessionId ) ) ;
329
+ }
330
+
331
+ const machineIndex = machines . findIndex ( machine =>
332
+ machine . docker . container === containerId ) ;
333
+
334
+ if ( machineIndex === - 1 ) {
335
+ callback ( new Error ( 'Wrong container ID' ) ) ;
336
+ }
337
+
338
+ _destroy ( machines [ machineIndex ] , ( error ) => {
339
+ if ( error ) {
340
+ callback ( error ) ;
341
+ }
342
+
343
+ machines . splice ( machineIndex , 1 ) ;
344
+ db . save ( ) ;
345
+ callback ( ) ;
346
+ } ) ;
347
+ } ;
348
+
349
+ function _destroy ( machine , callback ) {
350
+ const { container : containerId , host } = machine . docker ;
351
+
352
+ log ( 'destroy' , containerId . slice ( 0 , 16 ) , 'started' ) ;
353
+ docker . removeContainer ( { host, container : containerId } , error => {
354
+ if ( error ) {
355
+ log ( 'destroy' , containerId . slice ( 0 , 16 ) , error ) ;
356
+ callback ( error ) ;
357
+ return ;
358
+ }
359
+
360
+ callback ( ) ;
292
361
293
362
if ( ! machine . docker . image ) {
294
363
log ( 'destroy' , containerId . slice ( 0 , 16 ) , 'success' ) ;
@@ -307,7 +376,7 @@ exports.destroy = function (user, projectId, machineId, callback) {
307
376
db . save ( ) ;
308
377
} ) ;
309
378
} ) ;
310
- } ;
379
+ }
311
380
312
381
// Install or overwrite a configuration file in all the user's containers.
313
382
exports . deployConfigurationInAllContainers = function ( user , file ) {
@@ -452,6 +521,33 @@ function getOrCreateNewMachine (user, projectId) {
452
521
return machine ;
453
522
}
454
523
524
+ function createNewTemporaryMachine ( sessionId , projectId ) {
525
+ const project = getProject ( projectId ) ;
526
+ const temporaryMachines = db . get ( 'temporaryMachines' ) ;
527
+ if ( ! ( sessionId in temporaryMachines ) ) {
528
+ temporaryMachines [ sessionId ] = [ ] ;
529
+ }
530
+
531
+ const machines = temporaryMachines [ sessionId ] ;
532
+
533
+ const machine = {
534
+ properties : {
535
+ name : project . name + ' #' + machines . length ,
536
+ } ,
537
+ status : 'new' ,
538
+ docker : {
539
+ host : '' ,
540
+ container : '' ,
541
+ ports : { } ,
542
+ logs : ''
543
+ } ,
544
+ data : { }
545
+ } ;
546
+ machines . push ( machine ) ;
547
+
548
+ return machine ;
549
+ }
550
+
455
551
// Get a unique available port starting from 42000.
456
552
function getPort ( ) {
457
553
const ports = db . get ( 'ports' ) ;
0 commit comments