diff --git a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts index 8192aca6..2155571b 100644 --- a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts +++ b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts @@ -30,8 +30,10 @@ const EMPTY_SERVER_PATHS: ServerPath = { 'anvil-manage-keys': {}, 'anvil-manage-power': {}, 'anvil-provision-server': {}, + 'anvil-safe-stop': {}, 'anvil-sync-shared': {}, 'anvil-update-system': {}, + 'striker-boot-machine': {}, 'striker-initialize-host': {}, 'striker-manage-install-target': {}, 'striker-manage-peers': {}, diff --git a/striker-ui-api/src/lib/request_handlers/command/buildHostPowerHandler.ts b/striker-ui-api/src/lib/request_handlers/command/buildHostPowerHandler.ts deleted file mode 100644 index 842f0d51..00000000 --- a/striker-ui-api/src/lib/request_handlers/command/buildHostPowerHandler.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { RequestHandler } from 'express'; - -import SERVER_PATHS from '../../consts/SERVER_PATHS'; - -import { job } from '../../accessModule'; -import { stderr } from '../../shell'; - -type DistinctJobParams = Omit< - JobParams, - 'file' | 'line' | 'job_data' | 'job_progress' ->; - -const MANAGE_HOST_POWER_JOB_PARAMS: { - poweroff: DistinctJobParams; - reboot: DistinctJobParams; -} = { - poweroff: { - job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`, - job_name: 'poweroff::system', - job_title: 'job_0010', - job_description: 'job_0008', - }, - reboot: { - job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --reboot -y`, - job_name: 'reboot::system', - job_title: 'job_0009', - job_description: 'job_0006', - }, -}; - -export const buildHostPowerHandler: ( - task?: 'poweroff' | 'reboot', -) => RequestHandler = - (task = 'reboot') => - async (request, response) => { - const subParams: JobParams = { - file: __filename, - - ...MANAGE_HOST_POWER_JOB_PARAMS[task], - }; - - try { - await job(subParams); - } catch (subError) { - stderr(`Failed to ${task} host; CAUSE: ${subError}`); - - return response.status(500).send(); - } - - response.status(204).send(); - }; diff --git a/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts b/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts new file mode 100644 index 00000000..486e85b8 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts @@ -0,0 +1,139 @@ +import assert from 'assert'; +import { RequestHandler } from 'express'; + +import { LOCAL, REP_UUID, SERVER_PATHS } from '../../consts'; + +import { job, query } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stderr } from '../../shell'; + +/** + * Notes on power functions: + * * poweroff, reboot targets the striker this express app operates on + * * start, stop targets subnode or DR host + */ +const MANAGE_HOST_POWER_JOB_PARAMS: Record< + PowerTask, + BuildPowerJobParamsFunction +> = { + poweroff: () => ({ + job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`, + job_name: 'poweroff::system', + job_title: 'job_0010', + job_description: 'job_0008', + }), + reboot: () => ({ + job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --reboot -y`, + job_name: 'reboot::system', + job_title: 'job_0009', + job_description: 'job_0006', + }), + start: ({ uuid } = {}) => ({ + job_command: `${SERVER_PATHS.usr.sbin['striker-boot-machine'].self} --host-uuid '${uuid}'`, + job_description: 'job_0335', + job_name: `set_power::on`, + job_title: 'job_0334', + }), + stop: ({ isStopServers, uuid } = {}) => ({ + job_command: `${ + SERVER_PATHS.usr.sbin['anvil-safe-stop'].self + } --power-off ${isStopServers ? '--stop-servers' : ''}`, + job_description: 'job_0333', + job_host_uuid: uuid, + job_name: 'set_power::off', + job_title: 'job_0332', + }), +}; + +const queuePowerJob = async ( + task: PowerTask, + options?: BuildPowerJobParamsOptions, +) => { + const subParams: JobParams = { + file: __filename, + + ...MANAGE_HOST_POWER_JOB_PARAMS[task](options), + }; + + return await job(subParams); +}; + +export const buildPowerHandler: ( + task: PowerTask, +) => RequestHandler<{ uuid?: string }> = + (task) => async (request, response) => { + const { + params: { uuid }, + } = request; + + try { + if (uuid) { + assert( + REP_UUID.test(uuid), + `Param UUID must be a valid UUIDv4; got [${uuid}]`, + ); + } + } catch (error) { + stderr(`Failed to ${task} host; CAUSE: ${error}`); + + return response.status(400).send(); + } + + try { + await queuePowerJob(task, { uuid }); + } catch (error) { + stderr(`Failed to ${task} host ${uuid ?? LOCAL}; CAUSE: ${error}`); + + return response.status(500).send(); + } + + response.status(204).send(); + }; + +export const buildAnPowerHandler: ( + task: Extract, +) => RequestHandler<{ uuid: string }> = (task) => async (request, response) => { + const { + params: { uuid }, + } = request; + + const anUuid = sanitize(uuid, 'string', { modifierType: 'sql' }); + + try { + assert( + REP_UUID.test(anUuid), + `Param UUID must be a valid UUIDv4; got: [${anUuid}]`, + ); + } catch (error) { + stderr(`Failed to assert value during power operation on anvil subnode`); + + return response.status(400).send(); + } + + let rows: [[node1Uuid: string, node2Uuid: string]]; + + try { + rows = await query( + `SELECT anvil_node1_host_uuid, anvil_node2_host_uuid + FROM anvils WHERE anvil_uuid = '${anUuid}';`, + ); + + assert.ok(rows.length, 'No entry found'); + } catch (error) { + stderr(`Failed to get anvil subnodes' UUID; CAUSE: ${error}`); + + return response.status(500).send(); + } + + for (const hostUuid of rows[0]) { + try { + await queuePowerJob(task, { isStopServers: true, uuid: hostUuid }); + } catch (error) { + stderr(`Failed to ${task} host ${hostUuid}; CAUSE: ${error}`); + + return response.status(500).send(); + } + } + + return response.status(204).send(); +}; diff --git a/striker-ui-api/src/lib/request_handlers/command/index.ts b/striker-ui-api/src/lib/request_handlers/command/index.ts index 2888de07..978368b1 100644 --- a/striker-ui-api/src/lib/request_handlers/command/index.ts +++ b/striker-ui-api/src/lib/request_handlers/command/index.ts @@ -1,5 +1,9 @@ export * from './getHostSSH'; -export * from './poweroffHost'; -export * from './rebootHost'; +export * from './poweroffStriker'; +export * from './rebootStriker'; export * from './runManifest'; +export * from './startAn'; +export * from './startSubnode'; +export * from './stopAn'; +export * from './stopSubnode'; export * from './updateSystem'; diff --git a/striker-ui-api/src/lib/request_handlers/command/poweroffHost.ts b/striker-ui-api/src/lib/request_handlers/command/poweroffHost.ts deleted file mode 100644 index 47dc21cb..00000000 --- a/striker-ui-api/src/lib/request_handlers/command/poweroffHost.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { buildHostPowerHandler } from './buildHostPowerHandler'; - -export const poweroffHost = buildHostPowerHandler('poweroff'); diff --git a/striker-ui-api/src/lib/request_handlers/command/poweroffStriker.ts b/striker-ui-api/src/lib/request_handlers/command/poweroffStriker.ts new file mode 100644 index 00000000..a7ad1541 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/poweroffStriker.ts @@ -0,0 +1,3 @@ +import { buildPowerHandler } from './buildPowerHandler'; + +export const poweroffStriker = buildPowerHandler('poweroff'); diff --git a/striker-ui-api/src/lib/request_handlers/command/rebootHost.ts b/striker-ui-api/src/lib/request_handlers/command/rebootHost.ts deleted file mode 100644 index 0ee5321b..00000000 --- a/striker-ui-api/src/lib/request_handlers/command/rebootHost.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { buildHostPowerHandler } from './buildHostPowerHandler'; - -export const rebootHost = buildHostPowerHandler('reboot'); diff --git a/striker-ui-api/src/lib/request_handlers/command/rebootStriker.ts b/striker-ui-api/src/lib/request_handlers/command/rebootStriker.ts new file mode 100644 index 00000000..ffb737e0 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/rebootStriker.ts @@ -0,0 +1,3 @@ +import { buildPowerHandler } from './buildPowerHandler'; + +export const rebootStriker = buildPowerHandler('reboot'); diff --git a/striker-ui-api/src/lib/request_handlers/command/startAn.ts b/striker-ui-api/src/lib/request_handlers/command/startAn.ts new file mode 100644 index 00000000..55f7246e --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/startAn.ts @@ -0,0 +1,3 @@ +import { buildAnPowerHandler } from './buildPowerHandler'; + +export const startAn = buildAnPowerHandler('start'); diff --git a/striker-ui-api/src/lib/request_handlers/command/startSubnode.ts b/striker-ui-api/src/lib/request_handlers/command/startSubnode.ts new file mode 100644 index 00000000..f64b2066 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/startSubnode.ts @@ -0,0 +1,3 @@ +import { buildPowerHandler } from './buildPowerHandler'; + +export const startSubnode = buildPowerHandler('start'); diff --git a/striker-ui-api/src/lib/request_handlers/command/stopAn.ts b/striker-ui-api/src/lib/request_handlers/command/stopAn.ts new file mode 100644 index 00000000..cabe913a --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/stopAn.ts @@ -0,0 +1,3 @@ +import { buildAnPowerHandler } from './buildPowerHandler'; + +export const stopAn = buildAnPowerHandler('stop'); diff --git a/striker-ui-api/src/lib/request_handlers/command/stopSubnode.ts b/striker-ui-api/src/lib/request_handlers/command/stopSubnode.ts new file mode 100644 index 00000000..01245de3 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/stopSubnode.ts @@ -0,0 +1,3 @@ +import { buildPowerHandler } from './buildPowerHandler'; + +export const stopSubnode = buildPowerHandler('stop'); diff --git a/striker-ui-api/src/routes/command.ts b/striker-ui-api/src/routes/command.ts index d547aba1..9eab5515 100644 --- a/striker-ui-api/src/routes/command.ts +++ b/striker-ui-api/src/routes/command.ts @@ -2,9 +2,13 @@ import express from 'express'; import { getHostSSH, - poweroffHost, - rebootHost, + poweroffStriker, + rebootStriker, runManifest, + startAn, + startSubnode, + stopAn, + stopSubnode, updateSystem, } from '../lib/request_handlers/command'; @@ -12,9 +16,13 @@ const router = express.Router(); router .put('/inquire-host', getHostSSH) - .put('/poweroff-host', poweroffHost) - .put('/reboot-host', rebootHost) + .put('/poweroff-host', poweroffStriker) + .put('/reboot-host', rebootStriker) .put('/run-manifest/:manifestUuid', runManifest) + .put('/start-an/:uuid', startAn) + .put('/start-subnode/:uuid', startSubnode) + .put('/stop-an/:uuid', stopAn) + .put('/stop-subnode/:uuid', stopSubnode) .put('/update-system', updateSystem); export default router; diff --git a/striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts b/striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts new file mode 100644 index 00000000..8bb792ac --- /dev/null +++ b/striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts @@ -0,0 +1,15 @@ +type PowerTask = 'poweroff' | 'reboot' | 'start' | 'stop'; + +type PowerJobParams = Omit< + JobParams, + 'file' | 'line' | 'job_data' | 'job_progress' +>; + +type BuildPowerJobParamsOptions = { + isStopServers?: boolean; + uuid?: string; +}; + +type BuildPowerJobParamsFunction = ( + options?: BuildPowerJobParamsOptions, +) => PowerJobParams;