From 4d9c2e79f0cc0326d61236359b08999b856b1278 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Sat, 6 May 2023 00:31:55 -0400 Subject: [PATCH] fix(striker-ui-api): replace handle subnode cluster membership endpoint --- striker-ui-api/src/lib/consts/SERVER_PATHS.ts | 1 + .../command/buildMembershipHandler.ts | 123 ++++++++++++++++++ .../command/buildPowerHandler.ts | 12 +- .../src/lib/request_handlers/command/index.ts | 2 + .../lib/request_handlers/command/joinAn.ts | 3 + .../lib/request_handlers/command/leaveAn.ts | 3 + striker-ui-api/src/routes/command.ts | 4 + .../types/BuildMembershipHandlerFunction.d.ts | 12 ++ ...on.d.ts => BuildPowerHandlerFunction.d.ts} | 5 +- 9 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 striker-ui-api/src/lib/request_handlers/command/buildMembershipHandler.ts create mode 100644 striker-ui-api/src/lib/request_handlers/command/joinAn.ts create mode 100644 striker-ui-api/src/lib/request_handlers/command/leaveAn.ts create mode 100644 striker-ui-api/src/types/BuildMembershipHandlerFunction.d.ts rename striker-ui-api/src/types/{BuildPowerJobParamsFunction.d.ts => BuildPowerHandlerFunction.d.ts} (72%) diff --git a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts index 2155571b..6fad8458 100644 --- a/striker-ui-api/src/lib/consts/SERVER_PATHS.ts +++ b/striker-ui-api/src/lib/consts/SERVER_PATHS.ts @@ -30,6 +30,7 @@ const EMPTY_SERVER_PATHS: ServerPath = { 'anvil-manage-keys': {}, 'anvil-manage-power': {}, 'anvil-provision-server': {}, + 'anvil-safe-start': {}, 'anvil-safe-stop': {}, 'anvil-sync-shared': {}, 'anvil-update-system': {}, diff --git a/striker-ui-api/src/lib/request_handlers/command/buildMembershipHandler.ts b/striker-ui-api/src/lib/request_handlers/command/buildMembershipHandler.ts new file mode 100644 index 00000000..f3fc0dba --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/buildMembershipHandler.ts @@ -0,0 +1,123 @@ +import assert from 'assert'; +import { RequestHandler } from 'express'; + +import { REP_UUID, SERVER_PATHS } from '../../consts'; + +import { job, query } from '../../accessModule'; +import { sanitize } from '../../sanitize'; +import { stderr } from '../../shell'; + +const MAP_TO_MEMBERSHIP_JOB_PARAMS_BUILDER: Record< + MembershipTask, + BuildMembershipJobParamsFunction +> = { + join: async (uuid, { isActiveMember } = {}) => { + // Host is already a cluster member + if (isActiveMember) return undefined; + + const rows: [[number]] = await query( + `SELECT + CASE + WHEN host_status = 'online' + THEN CAST('1' AS BOOLEAN) + ELSE CAST('0' AS BOOLEAN) + END + FROM hosts WHERE host_uuid = '${uuid}';`, + ); + + assert.ok(rows.length, 'No entry found'); + + const [[isOnline]] = rows; + + return isOnline + ? { + job_command: SERVER_PATHS.usr.sbin['anvil-safe-start'].self, + job_description: 'job_0337', + job_host_uuid: uuid, + job_name: 'set_membership::join', + job_title: 'job_0336', + } + : undefined; + }, + leave: async (uuid, { isActiveMember } = {}) => + isActiveMember + ? { + job_command: SERVER_PATHS.usr.sbin['anvil-safe-stop'].self, + job_description: 'job_0339', + job_host_uuid: uuid, + job_name: 'set_membership::leave', + job_title: 'job_0338', + } + : undefined, +}; + +export const buildMembershipHandler: ( + task: MembershipTask, +) => RequestHandler<{ uuid: string }> = (task) => async (request, response) => { + const { + params: { uuid }, + } = request; + + const hostUuid = sanitize(uuid, 'string', { modifierType: 'sql' }); + + try { + assert( + REP_UUID.test(hostUuid), + `Param UUID must be a valid UUIDv4; got: [${hostUuid}]`, + ); + } catch (error) { + stderr( + `Failed to assert value when changing host membership; CAUSE: ${error}`, + ); + + return response.status(500).send(); + } + + let rows: [ + [ + hostInCcm: NumberBoolean, + hostCrmdMember: NumberBoolean, + hostClusterMember: NumberBoolean, + ], + ]; + + try { + rows = await query( + `SELECT + scan_cluster_node_in_ccm, + scan_cluster_node_crmd_member, + scan_cluster_node_cluster_member + FROM scan_cluster_nodes + WHERE scan_cluster_node_host_uuid = '${hostUuid}';`, + ); + + assert.ok(rows.length, `No entry found`); + } catch (error) { + stderr(`Failed to get cluster status of host ${hostUuid}; CAUSE: ${error}`); + + return response.status(500).send(); + } + + const isActiveMember = rows[0].every((stage) => Boolean(stage)); + + try { + const restParams = await MAP_TO_MEMBERSHIP_JOB_PARAMS_BUILDER[task]( + hostUuid, + { + isActiveMember, + }, + ); + + if (restParams) { + await job({ file: __filename, ...restParams }); + } + } catch (error) { + stderr( + `Failed to initiate ${task} cluster for 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/buildPowerHandler.ts b/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts index 486e85b8..7dde98fe 100644 --- a/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts +++ b/striker-ui-api/src/lib/request_handlers/command/buildPowerHandler.ts @@ -12,21 +12,21 @@ import { stderr } from '../../shell'; * * poweroff, reboot targets the striker this express app operates on * * start, stop targets subnode or DR host */ -const MANAGE_HOST_POWER_JOB_PARAMS: Record< +const MAP_TO_POWER_JOB_PARAMS_BUILDER: Record< PowerTask, BuildPowerJobParamsFunction > = { poweroff: () => ({ job_command: `${SERVER_PATHS.usr.sbin['anvil-manage-power'].self} --poweroff -y`, + job_description: 'job_0008', 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_description: 'job_0006', 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}'`, @@ -49,13 +49,13 @@ const queuePowerJob = async ( task: PowerTask, options?: BuildPowerJobParamsOptions, ) => { - const subParams: JobParams = { + const params: JobParams = { file: __filename, - ...MANAGE_HOST_POWER_JOB_PARAMS[task](options), + ...MAP_TO_POWER_JOB_PARAMS_BUILDER[task](options), }; - return await job(subParams); + return await job(params); }; export const buildPowerHandler: ( 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 978368b1..92feabe7 100644 --- a/striker-ui-api/src/lib/request_handlers/command/index.ts +++ b/striker-ui-api/src/lib/request_handlers/command/index.ts @@ -1,4 +1,6 @@ export * from './getHostSSH'; +export * from './joinAn'; +export * from './leaveAn'; export * from './poweroffStriker'; export * from './rebootStriker'; export * from './runManifest'; diff --git a/striker-ui-api/src/lib/request_handlers/command/joinAn.ts b/striker-ui-api/src/lib/request_handlers/command/joinAn.ts new file mode 100644 index 00000000..02acd8cb --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/joinAn.ts @@ -0,0 +1,3 @@ +import { buildMembershipHandler } from './buildMembershipHandler'; + +export const joinAn = buildMembershipHandler('join'); diff --git a/striker-ui-api/src/lib/request_handlers/command/leaveAn.ts b/striker-ui-api/src/lib/request_handlers/command/leaveAn.ts new file mode 100644 index 00000000..5bd47c88 --- /dev/null +++ b/striker-ui-api/src/lib/request_handlers/command/leaveAn.ts @@ -0,0 +1,3 @@ +import { buildMembershipHandler } from './buildMembershipHandler'; + +export const leaveAn = buildMembershipHandler('leave'); diff --git a/striker-ui-api/src/routes/command.ts b/striker-ui-api/src/routes/command.ts index 9eab5515..0aac6ecf 100644 --- a/striker-ui-api/src/routes/command.ts +++ b/striker-ui-api/src/routes/command.ts @@ -2,6 +2,8 @@ import express from 'express'; import { getHostSSH, + joinAn, + leaveAn, poweroffStriker, rebootStriker, runManifest, @@ -16,6 +18,8 @@ const router = express.Router(); router .put('/inquire-host', getHostSSH) + .put('/join-an/:uuid', joinAn) + .put('/leave-an/:uuid', leaveAn) .put('/poweroff-host', poweroffStriker) .put('/reboot-host', rebootStriker) .put('/run-manifest/:manifestUuid', runManifest) diff --git a/striker-ui-api/src/types/BuildMembershipHandlerFunction.d.ts b/striker-ui-api/src/types/BuildMembershipHandlerFunction.d.ts new file mode 100644 index 00000000..7b7394e7 --- /dev/null +++ b/striker-ui-api/src/types/BuildMembershipHandlerFunction.d.ts @@ -0,0 +1,12 @@ +type MembershipTask = 'join' | 'leave'; + +type MembershipJobParams = Omit; + +type BuildMembershipJobParamsOptions = { + isActiveMember?: boolean; +}; + +type BuildMembershipJobParamsFunction = ( + uuid: string, + options?: BuildMembershipJobParamsOptions, +) => Promise; diff --git a/striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts b/striker-ui-api/src/types/BuildPowerHandlerFunction.d.ts similarity index 72% rename from striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts rename to striker-ui-api/src/types/BuildPowerHandlerFunction.d.ts index 8bb792ac..65e2cc29 100644 --- a/striker-ui-api/src/types/BuildPowerJobParamsFunction.d.ts +++ b/striker-ui-api/src/types/BuildPowerHandlerFunction.d.ts @@ -1,9 +1,6 @@ type PowerTask = 'poweroff' | 'reboot' | 'start' | 'stop'; -type PowerJobParams = Omit< - JobParams, - 'file' | 'line' | 'job_data' | 'job_progress' ->; +type PowerJobParams = Omit; type BuildPowerJobParamsOptions = { isStopServers?: boolean;