@@ -15,6 +15,8 @@ import {
1515 SuccessResponse ,
1616 Tags ,
1717} from 'tsoa'
18+ import { asyncEach } from '@vates/async-each'
19+ import { type Defer , defer } from 'golike-defer'
1820import { json } from 'express'
1921import type { Request as ExRequest , Response as ExResponse } from 'express'
2022import { inject } from 'inversify'
@@ -30,6 +32,7 @@ import type {
3032 XoMessage ,
3133 XoPif ,
3234 XoTask ,
35+ XoVm ,
3336 XsPatches ,
3437} from '@vates/types'
3538
@@ -345,7 +348,97 @@ export class HostController extends XapiXoController<XoHost> {
345348 taskProperties : {
346349 name : 'reconfigure host management interface' ,
347350 objectId : hostId ,
348- args : body ,
351+ params : body ,
352+ } ,
353+ } )
354+ }
355+
356+ /**
357+ * Disable a host.
358+ *
359+ * Set `evacuate` to `true` to also evacuate all running VMs to other hosts in the pool.
360+ *
361+ * Use `vmIdsToForceMigrate` to unblock VMs whose migration is currently blocked (e.g. by `pool_migrate` or `migrate_send` blocked operations).
362+ *
363+ * Use `force` to ignore evacuation errors.
364+ *
365+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
366+ * @example body { "evacuate": true, "vmIdsToForceMigrate": ["f07ab729-c0e8-721c-45ec-f11276377030"] }
367+ */
368+ @Example ( taskLocation )
369+ @Post ( '{id}/actions/disable' )
370+ @Middlewares ( json ( ) )
371+ @SuccessResponse ( asynchronousActionResp . status , asynchronousActionResp . description )
372+ @Response ( noContentResp . status , noContentResp . description )
373+ @Response ( notFoundResp . status , notFoundResp . description )
374+ @Response ( invalidParametersResp . status , invalidParametersResp . description )
375+ @Response ( internalServerErrorResp . status , internalServerErrorResp . description )
376+ disable (
377+ @Path ( ) id : string ,
378+ // mark `evacuate` as optional to workaround a TSOA issue. See https://github.com/lukeautry/tsoa/pull/1840
379+ @Body ( ) body ?: { evacuate ?: false } | { evacuate : true ; force ?: boolean ; vmIdsToForceMigrate ?: string [ ] } ,
380+ @Query ( ) sync ?: boolean
381+ ) : CreateActionReturnType < void > {
382+ const hostId = id as XoHost [ 'id' ]
383+ const action = defer ( async ( $defer : Defer ) => {
384+ const xapiHost = this . getXapiObject ( hostId )
385+ const xapi = xapiHost . $xapi
386+
387+ if ( body ?. evacuate !== true ) {
388+ await xapi . call ( 'host.disable' , xapiHost . $ref )
389+ return
390+ }
391+
392+ if ( body . vmIdsToForceMigrate !== undefined ) {
393+ await asyncEach ( body . vmIdsToForceMigrate , async vmId => {
394+ const xoVm = this . restApi . getObject < XoVm > ( vmId as XoVm [ 'id' ] , 'VM' )
395+ for ( const operation of [ 'pool_migrate' , 'migrate_send' ] as const ) {
396+ const reason = xoVm . blockedOperations [ operation ]
397+ if ( reason !== undefined ) {
398+ await xapi . call ( 'VM.remove_from_blocked_operations' , xoVm . _xapiRef , operation )
399+ $defer ( ( ) => xapi . call ( 'VM.add_to_blocked_operations' , xoVm . _xapiRef , operation , reason ) )
400+ }
401+ }
402+ } )
403+ }
404+
405+ await xapi . clearHost ( xapiHost , body . force )
406+ } )
407+
408+ return this . createAction < void > ( action , {
409+ sync,
410+ statusCode : noContentResp . status ,
411+ taskProperties : {
412+ name : body ?. evacuate === true ? 'disable and evacuate host' : 'disable host' ,
413+ objectId : hostId ,
414+ params : body ,
415+ } ,
416+ } )
417+ }
418+
419+ /**
420+ * Enable a host, taking it out of disabled state.
421+ *
422+ * @example id "b61a5c92-700e-4966-a13b-00633f03eea8"
423+ */
424+ @Example ( taskLocation )
425+ @Post ( '{id}/actions/enable' )
426+ @SuccessResponse ( asynchronousActionResp . status , asynchronousActionResp . description )
427+ @Response ( noContentResp . status , noContentResp . description )
428+ @Response ( notFoundResp . status , notFoundResp . description )
429+ @Response ( internalServerErrorResp . status , internalServerErrorResp . description )
430+ enable ( @Path ( ) id : string , @Query ( ) sync ?: boolean ) : CreateActionReturnType < void > {
431+ const hostId = id as XoHost [ 'id' ]
432+ const action = async ( ) => {
433+ await this . getXapiObject ( hostId ) . $xapi . enableHost ( hostId )
434+ }
435+
436+ return this . createAction < void > ( action , {
437+ sync,
438+ statusCode : noContentResp . status ,
439+ taskProperties : {
440+ name : 'enable host' ,
441+ objectId : hostId ,
349442 } ,
350443 } )
351444 }
0 commit comments