commit
c1e4380a64
285 changed files with 7430 additions and 3094 deletions
@ -0,0 +1,9 @@ |
|||||||
|
MAINTAINERCLEANFILES = Makefile.in
|
||||||
|
|
||||||
|
libvirtdir = ${sysconfdir}/libvirt
|
||||||
|
hooksdir = ${libvirtdir}/hooks
|
||||||
|
qemuddir = ${hooksdir}/qemu.d
|
||||||
|
|
||||||
|
dist_qemud_SCRIPTS = \
|
||||||
|
hooks/qemu.d/ws
|
||||||
|
|
@ -0,0 +1,51 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# TODO: re-enable after the possible libvirt deadlock is fixed. |
||||||
|
exit 0 |
||||||
|
|
||||||
|
{ |
||||||
|
echo "wsargs=$@" |
||||||
|
|
||||||
|
domain_xml=$(</dev/stdin) |
||||||
|
guest_name="$1" |
||||||
|
operation="$2" |
||||||
|
|
||||||
|
# Operation migrate will: |
||||||
|
# 1. Trigger migrate->prepare->start->started operation on the destination host. |
||||||
|
# 2. Trigger stopped->release operations on the source host. |
||||||
|
if [[ "$operation" == "started" || "$operation" == "stopped" ]] |
||||||
|
then |
||||||
|
ws_open_flag="" |
||||||
|
ws_port_flag="" |
||||||
|
ws_suuid_flag="" |
||||||
|
|
||||||
|
if [[ "$operation" == "started" ]] |
||||||
|
then |
||||||
|
ws_open_flag="--open" |
||||||
|
|
||||||
|
# Cannot call $ virsh vncdisplay... because libvirt hooks |
||||||
|
# cannot call anything related to libvirt, i.e., virsh, because |
||||||
|
# a deadlock will happen. |
||||||
|
server_vnc_port=$( grep "<graphics.*type=['\"]vnc['\"]" <<<$domain_xml | grep -oPm1 "(?<=port=['\"])\d+" ) |
||||||
|
ws_port_flag="--server-vnc-port ${server_vnc_port}" |
||||||
|
|
||||||
|
server_uuid=$( grep -oPm1 "(?<=<uuid>)[^\s]+(?=<)" <<<$domain_xml ) |
||||||
|
ws_suuid_flag="--server-uuid ${server_uuid}" |
||||||
|
|
||||||
|
local_host_uuid=$(</etc/anvil/host.uuid) |
||||||
|
|
||||||
|
update_sql="UPDATE servers SET server_host_uuid = '$local_host_uuid' WHERE server_name = '$guest_name';" |
||||||
|
echo "wsupdate=$update_sql" |
||||||
|
anvil-access-module --query "$update_sql" --mode write |
||||||
|
fi |
||||||
|
|
||||||
|
ws_command_args="--server \"$guest_name\" $ws_suuid_flag --server-host-uuid local $ws_port_flag --component ws $ws_open_flag" |
||||||
|
echo "wscmd=$ws_command_args" |
||||||
|
striker-manage-vnc-pipes --server "$guest_name" $ws_suuid_flag --server-host-uuid local $ws_port_flag --component ws $ws_open_flag |
||||||
|
echo "wscmd_exit=$?" |
||||||
|
|
||||||
|
# Don't interrupt libvirt regardless of whether websockify gets setup |
||||||
|
# successfully. |
||||||
|
exit 0 |
||||||
|
fi |
||||||
|
} >>/var/log/anvil.log |
File diff suppressed because one or more lines are too long
@ -0,0 +1,14 @@ |
|||||||
|
export const buildJobData = <T extends [string, unknown][]>({ |
||||||
|
entries, |
||||||
|
getValue = (v) => String(v), |
||||||
|
}: { |
||||||
|
entries: T; |
||||||
|
getValue?: (value: T[number][1]) => string; |
||||||
|
}) => |
||||||
|
entries |
||||||
|
.reduce<string>((previous, [key, value]) => { |
||||||
|
previous += `${key}=${getValue(value)}\\n`; |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, '') |
||||||
|
.trim(); |
@ -0,0 +1,4 @@ |
|||||||
|
import { cap } from './cap'; |
||||||
|
|
||||||
|
export const camel = (...[head, ...rest]: string[]): string => |
||||||
|
rest.reduce<string>((previous, part) => `${previous}${cap(part)}`, head); |
@ -0,0 +1,3 @@ |
|||||||
|
import { COOKIE_PREFIX } from './consts'; |
||||||
|
|
||||||
|
export const cname = (postfix: string) => `${COOKIE_PREFIX}.${postfix}`; |
@ -0,0 +1,42 @@ |
|||||||
|
import { resolveGid, resolveUid } from '../shell'; |
||||||
|
|
||||||
|
/** |
||||||
|
* The prefix of every cookie used by the express app. |
||||||
|
* |
||||||
|
* @default 'suiapi' |
||||||
|
*/ |
||||||
|
export const COOKIE_PREFIX = process.env.COOKIE_PREFIX ?? 'suiapi'; |
||||||
|
|
||||||
|
/** |
||||||
|
* The fallback job progress value when queuing jobs. |
||||||
|
* |
||||||
|
* Ignore jobs by setting this to `100`. |
||||||
|
* |
||||||
|
* @default 0 |
||||||
|
*/ |
||||||
|
export const DEFAULT_JOB_PROGRESS: number = Number.parseInt( |
||||||
|
process.env.DEFAULT_JOB_PROGRESS ?? '0', |
||||||
|
); |
||||||
|
|
||||||
|
/** |
||||||
|
* Port to use by the express app. |
||||||
|
* |
||||||
|
* @default 8080 |
||||||
|
*/ |
||||||
|
export const PORT = Number.parseInt(process.env.PORT ?? '8080'); |
||||||
|
|
||||||
|
/** |
||||||
|
* Process user identifier. Also used to set ownership on the access daemon. |
||||||
|
* |
||||||
|
* @default 'striker-ui-api' |
||||||
|
*/ |
||||||
|
export const PUID = resolveUid(process.env.PUID ?? 'striker-ui-api'); |
||||||
|
|
||||||
|
/** |
||||||
|
* Process group identifier. Also used to set ownership on the access daemon. |
||||||
|
* |
||||||
|
* Defaults to the value set in process user identifier. |
||||||
|
* |
||||||
|
* @default PUID |
||||||
|
*/ |
||||||
|
export const PGID = resolveGid(process.env.PGID ?? PUID); |
@ -1,4 +1,2 @@ |
|||||||
// Unit: bytes
|
// Unit: bytes
|
||||||
const NODE_AND_DR_RESERVED_MEMORY_SIZE = 8589934592; |
export const NODE_AND_DR_RESERVED_MEMORY_SIZE = 8589934592; |
||||||
|
|
||||||
export default NODE_AND_DR_RESERVED_MEMORY_SIZE; |
|
||||||
|
@ -1,5 +0,0 @@ |
|||||||
import { resolveGid, resolveUid } from '../shell'; |
|
||||||
|
|
||||||
export const PUID = resolveUid(process.env.PUID ?? 'striker-ui-api'); |
|
||||||
|
|
||||||
export const PGID = resolveGid(process.env.PGID ?? PUID); |
|
@ -1 +0,0 @@ |
|||||||
export const PORT = process.env.PORT ?? 8080; |
|
@ -0,0 +1,56 @@ |
|||||||
|
import { buildNetworkLinkConfig } from './buildNetworkLinkConfig'; |
||||||
|
import { cvar } from '../varn'; |
||||||
|
|
||||||
|
export const buildNetworkConfig = ( |
||||||
|
networks: InitializeStrikerNetworkForm[], |
||||||
|
{ |
||||||
|
netconfStep = 2, |
||||||
|
netcountStep = 1, |
||||||
|
}: { |
||||||
|
netconfStep?: number; |
||||||
|
netcountStep?: number; |
||||||
|
} = {}, |
||||||
|
): FormConfigData => { |
||||||
|
const { counters: ncounts, data: cdata } = networks.reduce<{ |
||||||
|
counters: Record<InitializeStrikerNetworkForm['type'], number>; |
||||||
|
data: FormConfigData; |
||||||
|
}>( |
||||||
|
(previous, { createBridge, interfaces, ipAddress, subnetMask, type }) => { |
||||||
|
const { counters } = previous; |
||||||
|
|
||||||
|
counters[type] = counters[type] ? counters[type] + 1 : 1; |
||||||
|
|
||||||
|
const networkShortName = `${type}${counters[type]}`; |
||||||
|
|
||||||
|
previous.data = { |
||||||
|
...previous.data, |
||||||
|
[cvar(netconfStep, `${networkShortName}_ip`)]: { |
||||||
|
step: netconfStep, |
||||||
|
value: ipAddress, |
||||||
|
}, |
||||||
|
[cvar(netconfStep, `${networkShortName}_subnet_mask`)]: { |
||||||
|
step: netconfStep, |
||||||
|
value: subnetMask, |
||||||
|
}, |
||||||
|
...buildNetworkLinkConfig(networkShortName, interfaces), |
||||||
|
}; |
||||||
|
|
||||||
|
if (createBridge) { |
||||||
|
previous.data[cvar(netconfStep, `${networkShortName}_create_bridge`)] = |
||||||
|
{ |
||||||
|
step: netconfStep, |
||||||
|
value: createBridge, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, |
||||||
|
{ counters: {}, data: {} }, |
||||||
|
); |
||||||
|
|
||||||
|
Object.entries(ncounts).forEach(([ntype, ncount]) => { |
||||||
|
cdata[cvar(netcountStep, `${ntype}_count`)] = { value: ncount }; |
||||||
|
}); |
||||||
|
|
||||||
|
return cdata; |
||||||
|
}; |
@ -0,0 +1,19 @@ |
|||||||
|
import { cvar } from '../varn'; |
||||||
|
|
||||||
|
export const buildNetworkLinkConfig = ( |
||||||
|
networkShortName: string, |
||||||
|
interfaces: InitializeStrikerNetworkForm['interfaces'], |
||||||
|
configStep = 2, |
||||||
|
) => |
||||||
|
interfaces.reduce<FormConfigData>((previous, iface, index) => { |
||||||
|
if (iface) { |
||||||
|
const { networkInterfaceMACAddress } = iface; |
||||||
|
const linkNumber = index + 1; |
||||||
|
|
||||||
|
previous[ |
||||||
|
cvar(configStep, `${networkShortName}_link${linkNumber}_mac_to_set`) |
||||||
|
] = { step: configStep, value: networkInterfaceMACAddress }; |
||||||
|
} |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, {}); |
@ -0,0 +1,2 @@ |
|||||||
|
export * from './buildNetworkConfig'; |
||||||
|
export * from './buildNetworkLinkConfig'; |
@ -0,0 +1,108 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
|
||||||
|
import { query } from '../../accessModule'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
const buildHostStateMessage = (postfix = 2) => `message_022${postfix}`; |
||||||
|
|
||||||
|
export const buildAnvilSummary = async ({ |
||||||
|
anvils, |
||||||
|
anvilUuid, |
||||||
|
hosts, |
||||||
|
}: { |
||||||
|
anvils: AnvilDataAnvilListHash; |
||||||
|
anvilUuid: string; |
||||||
|
hosts: AnvilDataHostListHash; |
||||||
|
}) => { |
||||||
|
const { |
||||||
|
anvil_uuid: { [anvilUuid]: ainfo }, |
||||||
|
} = anvils; |
||||||
|
|
||||||
|
if (!ainfo) |
||||||
|
throw new Error(`Anvil information not found with UUID ${anvilUuid}`); |
||||||
|
|
||||||
|
const { |
||||||
|
anvil_name: aname, |
||||||
|
anvil_node1_host_uuid: n1uuid, |
||||||
|
anvil_node2_host_uuid: n2uuid, |
||||||
|
} = ainfo; |
||||||
|
|
||||||
|
const result: AnvilDetailSummary = { |
||||||
|
anvil_name: aname, |
||||||
|
anvil_state: 'optimal', |
||||||
|
anvil_uuid: anvilUuid, |
||||||
|
hosts: [], |
||||||
|
}; |
||||||
|
|
||||||
|
for (const huuid of [n1uuid, n2uuid]) { |
||||||
|
const { |
||||||
|
host_uuid: { |
||||||
|
[huuid]: { host_status: hstatus, short_host_name: hname }, |
||||||
|
}, |
||||||
|
} = hosts; |
||||||
|
|
||||||
|
const { hosts: rhosts } = result; |
||||||
|
|
||||||
|
const hsummary: AnvilDetailHostSummary = { |
||||||
|
host_name: hname, |
||||||
|
host_uuid: huuid, |
||||||
|
maintenance_mode: false, |
||||||
|
state: 'offline', |
||||||
|
state_message: buildHostStateMessage(), |
||||||
|
state_percent: 0, |
||||||
|
}; |
||||||
|
|
||||||
|
rhosts.push(hsummary); |
||||||
|
|
||||||
|
if (hstatus !== 'online') continue; |
||||||
|
|
||||||
|
let rows: [ |
||||||
|
inCcm: NumberBoolean, |
||||||
|
crmdMember: NumberBoolean, |
||||||
|
clusterMember: NumberBoolean, |
||||||
|
maintenanceMode: NumberBoolean, |
||||||
|
][]; |
||||||
|
|
||||||
|
try { |
||||||
|
rows = await query(`SELECT
|
||||||
|
scan_cluster_node_in_ccm, |
||||||
|
scan_cluster_node_crmd_member, |
||||||
|
scan_cluster_node_cluster_member, |
||||||
|
scan_cluster_node_maintenance_mode |
||||||
|
FROM |
||||||
|
scan_cluster_nodes |
||||||
|
WHERE |
||||||
|
scan_cluster_node_host_uuid = '${huuid}';`);
|
||||||
|
|
||||||
|
assert.ok(rows.length, 'No node cluster info'); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get node ${huuid} cluster status; CAUSE: ${error}`); |
||||||
|
|
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
const [[ccm, crmd, cluster, maintenance]] = rows; |
||||||
|
|
||||||
|
hsummary.maintenance_mode = Boolean(maintenance); |
||||||
|
|
||||||
|
if (cluster) { |
||||||
|
hsummary.state = 'online'; |
||||||
|
hsummary.state_message = buildHostStateMessage(3); |
||||||
|
hsummary.state_percent = 100; |
||||||
|
} else if (crmd) { |
||||||
|
hsummary.state = 'crmd'; |
||||||
|
hsummary.state_message = buildHostStateMessage(4); |
||||||
|
hsummary.state_percent = 75; |
||||||
|
} else if (ccm) { |
||||||
|
hsummary.state = 'in_ccm'; |
||||||
|
hsummary.state_message = buildHostStateMessage(5); |
||||||
|
hsummary.state_percent = 50; |
||||||
|
} else { |
||||||
|
hsummary.state = 'booted'; |
||||||
|
hsummary.state_message = buildHostStateMessage(6); |
||||||
|
hsummary.state_percent = 25; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
}; |
@ -0,0 +1,74 @@ |
|||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { query } from '../../accessModule'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
export const getAnvilCpu: RequestHandler<AnvilDetailParamsDictionary> = async ( |
||||||
|
request, |
||||||
|
response, |
||||||
|
) => { |
||||||
|
const { |
||||||
|
params: { anvilUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
let rCores: null | string; |
||||||
|
let rThreads: null | string; |
||||||
|
|
||||||
|
try { |
||||||
|
[[rCores = '', rThreads = '']] = await query< |
||||||
|
[[cpuCores: null | string, cpuThreads: null | string]] |
||||||
|
>( |
||||||
|
`SELECT
|
||||||
|
MIN(c.scan_hardware_cpu_cores) AS cores, |
||||||
|
MIN(c.scan_hardware_cpu_threads) AS threads |
||||||
|
FROM anvils AS a |
||||||
|
JOIN hosts AS b |
||||||
|
ON b.host_uuid IN ( |
||||||
|
a.anvil_node1_host_uuid, |
||||||
|
a.anvil_node2_host_uuid, |
||||||
|
a.anvil_dr1_host_uuid |
||||||
|
) |
||||||
|
JOIN scan_hardware AS c |
||||||
|
ON b.host_uuid = c.scan_hardware_host_uuid |
||||||
|
WHERE a.anvil_uuid = '${anvilUuid}';`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil ${anvilUuid} cpu info; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const cores = Number.parseInt(rCores); |
||||||
|
const threads = Number.parseInt(rThreads); |
||||||
|
|
||||||
|
let rAllocated: null | string; |
||||||
|
|
||||||
|
try { |
||||||
|
[[rAllocated = '']] = await query<[[cpuAllocated: null | string]]>( |
||||||
|
`SELECT
|
||||||
|
SUM( |
||||||
|
CAST( |
||||||
|
SUBSTRING( |
||||||
|
b.server_definition_xml, 'cores=''([\\d]*)''' |
||||||
|
) AS INTEGER |
||||||
|
) |
||||||
|
) AS allocated |
||||||
|
FROM servers AS a |
||||||
|
JOIN server_definitions AS b |
||||||
|
ON a.server_uuid = b.server_definition_server_uuid |
||||||
|
WHERE a.server_anvil_uuid = '${anvilUuid}';`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil ${anvilUuid} server cpu info; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const allocated = Number.parseInt(rAllocated); |
||||||
|
|
||||||
|
response.status(200).send({ |
||||||
|
allocated, |
||||||
|
cores, |
||||||
|
threads, |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,45 @@ |
|||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { getAnvilData, getHostData } from '../../accessModule'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
import { buildAnvilSummary } from './buildAnvilSummary'; |
||||||
|
|
||||||
|
export const getAnvilDetail: RequestHandler< |
||||||
|
AnvilDetailParamsDictionary, |
||||||
|
AnvilDetailSummary, |
||||||
|
undefined |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
params: { anvilUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
let anvils: AnvilDataAnvilListHash; |
||||||
|
let hosts: AnvilDataHostListHash; |
||||||
|
|
||||||
|
try { |
||||||
|
anvils = await getAnvilData(); |
||||||
|
hosts = await getHostData(); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil and/or host data; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let result: AnvilDetailSummary; |
||||||
|
|
||||||
|
try { |
||||||
|
result = await buildAnvilSummary({ |
||||||
|
anvils, |
||||||
|
anvilUuid, |
||||||
|
hosts, |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
stderr( |
||||||
|
`Failed to get summary of anvil node pair ${anvilUuid}; CAUSE: ${error}`, |
||||||
|
); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
response.status(200).send(result); |
||||||
|
}; |
@ -0,0 +1,121 @@ |
|||||||
|
import { RequestHandler } from 'express'; |
||||||
|
import { DataSizeUnit, dSize } from 'format-data-size'; |
||||||
|
|
||||||
|
import { NODE_AND_DR_RESERVED_MEMORY_SIZE } from '../../consts'; |
||||||
|
|
||||||
|
import { query } from '../../accessModule'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
export const getAnvilMemory: RequestHandler< |
||||||
|
AnvilDetailParamsDictionary |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
params: { anvilUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
let hostMemoryRows: [ |
||||||
|
hostUuid: string, |
||||||
|
minMemoryTotal: null | string, |
||||||
|
hostMemoryTotal: string, |
||||||
|
hostMemoryFree: string, |
||||||
|
hostSwapTotal: string, |
||||||
|
hostSwapFree: string, |
||||||
|
][]; |
||||||
|
|
||||||
|
try { |
||||||
|
hostMemoryRows = await query( |
||||||
|
`SELECT
|
||||||
|
b.host_uuid, |
||||||
|
MIN(c.scan_hardware_ram_total) AS min_memory_total, |
||||||
|
c.scan_hardware_ram_total, |
||||||
|
c.scan_hardware_memory_free, |
||||||
|
c.scan_hardware_swap_total, |
||||||
|
c.scan_hardware_swap_free |
||||||
|
FROM anvils AS a |
||||||
|
JOIN hosts AS b |
||||||
|
ON b.host_uuid IN ( |
||||||
|
a.anvil_node1_host_uuid, |
||||||
|
a.anvil_node2_host_uuid, |
||||||
|
a.anvil_dr1_host_uuid |
||||||
|
) |
||||||
|
JOIN scan_hardware AS c |
||||||
|
ON b.host_uuid = c.scan_hardware_host_uuid |
||||||
|
WHERE a.anvil_uuid = '${anvilUuid}' |
||||||
|
GROUP BY |
||||||
|
b.host_uuid, |
||||||
|
c.scan_hardware_ram_total, |
||||||
|
c.scan_hardware_memory_free, |
||||||
|
c.scan_hardware_swap_total, |
||||||
|
c.scan_hardware_swap_free |
||||||
|
ORDER BY b.host_name;`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil ${anvilUuid} memory info; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const { |
||||||
|
0: { 1: minTotal }, |
||||||
|
} = hostMemoryRows; |
||||||
|
|
||||||
|
if (minTotal === null) return response.status(404).send(); |
||||||
|
|
||||||
|
const hosts: AnvilDetailHostMemory[] = |
||||||
|
hostMemoryRows.map<AnvilDetailHostMemory>( |
||||||
|
([host_uuid, , total, free, swap_total, swap_free]) => ({ |
||||||
|
free, |
||||||
|
host_uuid, |
||||||
|
swap_free, |
||||||
|
swap_total, |
||||||
|
total, |
||||||
|
}), |
||||||
|
); |
||||||
|
|
||||||
|
let serverMemoryRows: [serverMemoryValue: string, serverMemoryUnit: string][]; |
||||||
|
|
||||||
|
try { |
||||||
|
serverMemoryRows = await query( |
||||||
|
`SELECT
|
||||||
|
CAST( |
||||||
|
SUBSTRING( |
||||||
|
a.server_definition_xml, 'memory.*>([\\d]*)</memory' |
||||||
|
) AS BIGINT |
||||||
|
) AS server_memory_value, |
||||||
|
SUBSTRING( |
||||||
|
a.server_definition_xml, 'memory.*unit=''([A-Za-z]*)''' |
||||||
|
) AS server_memory_unit |
||||||
|
FROM server_definitions AS a |
||||||
|
JOIN servers AS b |
||||||
|
ON b.server_uuid = a.server_definition_server_uuid |
||||||
|
WHERE server_anvil_uuid = '${anvilUuid}';`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil ${anvilUuid} server info; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let allocated = '0'; |
||||||
|
|
||||||
|
if (serverMemoryRows.length > 0) { |
||||||
|
allocated = String( |
||||||
|
serverMemoryRows.reduce<bigint>((previous, [mvalue, munit]) => { |
||||||
|
const serverMemory = |
||||||
|
dSize(mvalue, { |
||||||
|
fromUnit: munit as DataSizeUnit, |
||||||
|
toUnit: 'B', |
||||||
|
})?.value ?? '0'; |
||||||
|
|
||||||
|
return previous + BigInt(serverMemory); |
||||||
|
}, BigInt(0)), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(200).send({ |
||||||
|
allocated, |
||||||
|
hosts, |
||||||
|
reserved: String(NODE_AND_DR_RESERVED_MEMORY_SIZE), |
||||||
|
total: minTotal, |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,159 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { getAnvilData, getHostData, getNetworkData } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
const degrade = (current: string) => |
||||||
|
current === 'optimal' ? 'degraded' : current; |
||||||
|
|
||||||
|
const compare = (a: string, b: string) => (a > b ? 1 : -1); |
||||||
|
|
||||||
|
const buildSubnodeBonds = ( |
||||||
|
ifaces: AnvilDataSubnodeNetwork['interface'], |
||||||
|
): AnvilDetailSubnodeBond[] => { |
||||||
|
const bondList = Object.entries(ifaces) |
||||||
|
.sort(([an, { type: at }], [bn, { type: bt }]) => { |
||||||
|
const ab = at === 'bond'; |
||||||
|
const bb = bt === 'bond'; |
||||||
|
|
||||||
|
if (ab && bb) return compare(an, bn); |
||||||
|
if (ab) return -1; |
||||||
|
if (bb) return 1; |
||||||
|
|
||||||
|
return compare(an, bn); |
||||||
|
}) |
||||||
|
.reduce<{ |
||||||
|
[bondUuid: string]: AnvilDetailSubnodeBond; |
||||||
|
}>((previous, [ifname, ifvalue]) => { |
||||||
|
const { type } = ifvalue; |
||||||
|
|
||||||
|
if (type === 'bond') { |
||||||
|
const { active_interface, uuid: bondUuid } = |
||||||
|
ifvalue as AnvilDataHostNetworkBond; |
||||||
|
|
||||||
|
previous[bondUuid] = { |
||||||
|
active_interface, |
||||||
|
bond_name: ifname, |
||||||
|
bond_uuid: bondUuid, |
||||||
|
links: [], |
||||||
|
}; |
||||||
|
} else if (type === 'interface') { |
||||||
|
const { |
||||||
|
bond_uuid: bondUuid, |
||||||
|
operational, |
||||||
|
speed, |
||||||
|
uuid: linkUuid, |
||||||
|
} = ifvalue as AnvilDataHostNetworkLink; |
||||||
|
|
||||||
|
// Link without bond UUID can be ignored
|
||||||
|
if (!REP_UUID.test(bondUuid)) return previous; |
||||||
|
|
||||||
|
const { |
||||||
|
[bondUuid]: { active_interface, links }, |
||||||
|
} = previous; |
||||||
|
|
||||||
|
let linkState: string = operational === 'up' ? 'optimal' : 'down'; |
||||||
|
|
||||||
|
links.forEach((xLink) => { |
||||||
|
const { link_speed: xlSpeed, link_state: xlState } = xLink; |
||||||
|
|
||||||
|
if (xlSpeed < speed) { |
||||||
|
// Seen link is slower than current link, mark seen link as 'degraded'
|
||||||
|
xLink.link_state = degrade(xlState); |
||||||
|
} else if (xlSpeed > speed) { |
||||||
|
// Current link is slower than seen link, mark current link as 'degraded'
|
||||||
|
linkState = degrade(linkState); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
links.push({ |
||||||
|
is_active: ifname === active_interface, |
||||||
|
link_name: ifname, |
||||||
|
link_speed: speed, |
||||||
|
link_state: linkState, |
||||||
|
link_uuid: linkUuid, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, {}); |
||||||
|
|
||||||
|
return Object.values(bondList); |
||||||
|
}; |
||||||
|
|
||||||
|
export const getAnvilNetwork: RequestHandler< |
||||||
|
AnvilDetailParamsDictionary |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
params: { anvilUuid: rAnUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const anUuid = sanitize(rAnUuid, '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 get anvil network; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let ans: AnvilDataAnvilListHash; |
||||||
|
let hosts: AnvilDataHostListHash; |
||||||
|
|
||||||
|
try { |
||||||
|
ans = await getAnvilData(); |
||||||
|
hosts = await getHostData(); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil and host data; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const { |
||||||
|
anvil_uuid: { |
||||||
|
[anUuid]: { |
||||||
|
anvil_node1_host_uuid: n1uuid, |
||||||
|
anvil_node2_host_uuid: n2uuid, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} = ans; |
||||||
|
|
||||||
|
const rsbody: AnvilDetailNetworkSummary = { hosts: [] }; |
||||||
|
|
||||||
|
for (const hostUuid of [n1uuid, n2uuid]) { |
||||||
|
try { |
||||||
|
const { |
||||||
|
host_uuid: { |
||||||
|
[hostUuid]: { short_host_name: hostName }, |
||||||
|
}, |
||||||
|
} = hosts; |
||||||
|
|
||||||
|
const { [hostName]: subnodeNetwork } = await getNetworkData( |
||||||
|
hostUuid, |
||||||
|
hostName, |
||||||
|
); |
||||||
|
|
||||||
|
const { interface: ifaces } = subnodeNetwork as AnvilDataSubnodeNetwork; |
||||||
|
|
||||||
|
rsbody.hosts.push({ |
||||||
|
bonds: buildSubnodeBonds(ifaces), |
||||||
|
host_name: hostName, |
||||||
|
host_uuid: hostUuid, |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get host ${hostUuid} network data; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return response.json(rsbody); |
||||||
|
}; |
@ -0,0 +1,67 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { query } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
export const getAnvilStore: RequestHandler< |
||||||
|
AnvilDetailParamsDictionary |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
params: { anvilUuid: rAnUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const anUuid = sanitize(rAnUuid, '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 get anvil storage; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let rows: [uuid: string, name: string, size: string, free: string][]; |
||||||
|
|
||||||
|
try { |
||||||
|
rows = await query( |
||||||
|
`SELECT
|
||||||
|
DISTINCT ON (b.storage_group_uuid) storage_group_uuid, |
||||||
|
b.storage_group_name, |
||||||
|
d.scan_lvm_vg_size, |
||||||
|
d.scan_lvm_vg_free |
||||||
|
FROM anvils AS a |
||||||
|
JOIN storage_groups AS b |
||||||
|
ON a.anvil_uuid = b.storage_group_anvil_uuid |
||||||
|
JOIN storage_group_members AS c |
||||||
|
ON b.storage_group_uuid = c.storage_group_member_storage_group_uuid |
||||||
|
JOIN scan_lvm_vgs AS d |
||||||
|
ON c.storage_group_member_vg_uuid = d.scan_lvm_vg_internal_uuid |
||||||
|
WHERE a.anvil_uuid = '${anUuid}' |
||||||
|
ORDER BY b.storage_group_uuid, d.scan_lvm_vg_free;`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil storage summary; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const rsbody: AnvilDetailStoreSummary = { |
||||||
|
storage_groups: rows.map<AnvilDetailStore>( |
||||||
|
([sgUuid, sgName, sgSize, sgFree]) => ({ |
||||||
|
storage_group_free: sgFree, |
||||||
|
storage_group_name: sgName, |
||||||
|
storage_group_total: sgSize, |
||||||
|
storage_group_uuid: sgUuid, |
||||||
|
}), |
||||||
|
), |
||||||
|
}; |
||||||
|
|
||||||
|
return response.json(rsbody); |
||||||
|
}; |
@ -0,0 +1,33 @@ |
|||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { getAnvilData, getHostData } from '../../accessModule'; |
||||||
|
import { buildAnvilSummary } from './buildAnvilSummary'; |
||||||
|
import { stderr } from '../../shell'; |
||||||
|
|
||||||
|
export const getAnvilSummary: RequestHandler<unknown, AnvilSummary> = async ( |
||||||
|
request, |
||||||
|
response, |
||||||
|
) => { |
||||||
|
let anvils: AnvilDataAnvilListHash; |
||||||
|
let hosts: AnvilDataHostListHash; |
||||||
|
|
||||||
|
try { |
||||||
|
anvils = await getAnvilData(); |
||||||
|
hosts = await getHostData(); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get anvil and/or host data; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const { anvil_uuid: alist } = anvils; |
||||||
|
const result: AnvilSummary = { anvils: [] }; |
||||||
|
|
||||||
|
for (const auuid of Object.keys(alist)) { |
||||||
|
result.anvils.push( |
||||||
|
await buildAnvilSummary({ anvils, anvilUuid: auuid, hosts }), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
response.json(result); |
||||||
|
}; |
@ -0,0 +1,7 @@ |
|||||||
|
export * from './getAnvil'; |
||||||
|
export * from './getAnvilCpu'; |
||||||
|
export * from './getAnvilDetail'; |
||||||
|
export * from './getAnvilMemory'; |
||||||
|
export * from './getAnvilNetwork'; |
||||||
|
export * from './getAnvilStore'; |
||||||
|
export * from './getAnvilSummary'; |
@ -1,17 +1,19 @@ |
|||||||
import { RequestHandler } from 'express'; |
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { cname } from '../../cname'; |
||||||
import { stdout } from '../../shell'; |
import { stdout } from '../../shell'; |
||||||
|
|
||||||
export const logout: RequestHandler = (request, response) => { |
export const logout: RequestHandler = (request, response) => { |
||||||
request.session.destroy((error) => { |
request.session.destroy((error) => { |
||||||
let scode = 204; |
|
||||||
|
|
||||||
if (error) { |
if (error) { |
||||||
scode = 500; |
|
||||||
|
|
||||||
stdout(`Failed to destroy session upon logout; CAUSE: ${error}`); |
stdout(`Failed to destroy session upon logout; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
} |
} |
||||||
|
|
||||||
response.status(scode).send(); |
response.clearCookie(cname('user')); |
||||||
|
response.clearCookie(cname('sid')); |
||||||
|
|
||||||
|
return response.status(204).send(); |
||||||
}); |
}); |
||||||
}; |
}; |
||||||
|
@ -0,0 +1,65 @@ |
|||||||
|
import { AssertionError } from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { sanitize } from '../sanitize'; |
||||||
|
import { stderr, stdoutVar } from '../shell'; |
||||||
|
|
||||||
|
export const buildDeleteRequestHandler = |
||||||
|
< |
||||||
|
P extends Record<string, string> = Record<string, string>, |
||||||
|
ResBody = undefined, |
||||||
|
ReqBody extends Record<string, unknown> | undefined = Record< |
||||||
|
string, |
||||||
|
unknown |
||||||
|
>, |
||||||
|
ReqQuery = qs.ParsedQs, |
||||||
|
Locals extends Record<string, unknown> = Record<string, unknown>, |
||||||
|
>({ |
||||||
|
delete: handleDelete, |
||||||
|
key = 'uuid', |
||||||
|
listKey = 'uuids', |
||||||
|
}: { |
||||||
|
delete: ( |
||||||
|
list: string[], |
||||||
|
...handlerArgs: Parameters< |
||||||
|
RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> |
||||||
|
> |
||||||
|
) => Promise<void>; |
||||||
|
key?: string; |
||||||
|
listKey?: string; |
||||||
|
}): RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> => |
||||||
|
async (...handlerArgs) => { |
||||||
|
const { 0: request, 1: response } = handlerArgs; |
||||||
|
const { |
||||||
|
body: { [listKey]: rList } = {}, |
||||||
|
params: { [key]: rId }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const list = sanitize(rList, 'string[]'); |
||||||
|
|
||||||
|
if (rId !== undefined) { |
||||||
|
list.push(rId); |
||||||
|
} |
||||||
|
|
||||||
|
stdoutVar(list, `Process delete request with list: `); |
||||||
|
|
||||||
|
try { |
||||||
|
await handleDelete(list, ...handlerArgs); |
||||||
|
} catch (error) { |
||||||
|
let scode; |
||||||
|
|
||||||
|
if (error instanceof AssertionError) { |
||||||
|
scode = 400; |
||||||
|
|
||||||
|
stderr(`Failed to assert value during delete request; CAUSE: ${error}`); |
||||||
|
} else { |
||||||
|
scode = 500; |
||||||
|
|
||||||
|
stderr(`Failed to complete delete request; CAUSE: ${error}`); |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(scode).send(); |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(204).send(); |
||||||
|
}; |
@ -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(); |
|
||||||
}; |
|
@ -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(); |
||||||
|
}; |
@ -0,0 +1,151 @@ |
|||||||
|
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 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', |
||||||
|
}), |
||||||
|
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', |
||||||
|
}), |
||||||
|
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', |
||||||
|
}), |
||||||
|
startserver: ({ uuid } = {}) => ({ |
||||||
|
job_command: `${SERVER_PATHS.usr.sbin['anvil-boot-server'].self} --server-uuid '${uuid}'`, |
||||||
|
job_description: 'job_0341', |
||||||
|
job_name: 'set_power::server::on', |
||||||
|
job_title: 'job_0340', |
||||||
|
}), |
||||||
|
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', |
||||||
|
}), |
||||||
|
stopserver: ({ uuid } = {}) => ({ |
||||||
|
job_command: `${SERVER_PATHS.usr.sbin['anvil-shutdown-server'].self} --server-uuid '${uuid}'`, |
||||||
|
job_description: 'job_0343', |
||||||
|
job_name: 'set_power::server::off', |
||||||
|
job_title: 'job_0342', |
||||||
|
}), |
||||||
|
}; |
||||||
|
|
||||||
|
const queuePowerJob = async ( |
||||||
|
task: PowerTask, |
||||||
|
options?: BuildPowerJobParamsOptions, |
||||||
|
) => { |
||||||
|
const params: JobParams = { |
||||||
|
file: __filename, |
||||||
|
|
||||||
|
...MAP_TO_POWER_JOB_PARAMS_BUILDER[task](options), |
||||||
|
}; |
||||||
|
|
||||||
|
return await job(params); |
||||||
|
}; |
||||||
|
|
||||||
|
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}; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
await queuePowerJob(task, { uuid }); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to ${task} ${uuid ?? LOCAL}; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
response.status(204).send(); |
||||||
|
}; |
||||||
|
|
||||||
|
export const buildAnPowerHandler: ( |
||||||
|
task: Extract<PowerTask, 'start' | 'stop'>, |
||||||
|
) => 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(); |
||||||
|
}; |
@ -1,5 +1,15 @@ |
|||||||
export * from './getHostSSH'; |
export * from './getHostSSH'; |
||||||
export * from './poweroffHost'; |
export * from './joinAn'; |
||||||
export * from './rebootHost'; |
export * from './leaveAn'; |
||||||
|
export * from './manageVncSshTunnel'; |
||||||
|
export * from './poweroffStriker'; |
||||||
|
export * from './rebootStriker'; |
||||||
export * from './runManifest'; |
export * from './runManifest'; |
||||||
|
export * from './setMapNetwork'; |
||||||
|
export * from './startAn'; |
||||||
|
export * from './startServer'; |
||||||
|
export * from './startSubnode'; |
||||||
|
export * from './stopAn'; |
||||||
|
export * from './stopServer'; |
||||||
|
export * from './stopSubnode'; |
||||||
export * from './updateSystem'; |
export * from './updateSystem'; |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
import { buildMembershipHandler } from './buildMembershipHandler'; |
||||||
|
|
||||||
|
export const joinAn = buildMembershipHandler('join'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildMembershipHandler } from './buildMembershipHandler'; |
||||||
|
|
||||||
|
export const leaveAn = buildMembershipHandler('leave'); |
@ -0,0 +1,52 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { vncpipe } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, stdoutVar } from '../../shell'; |
||||||
|
|
||||||
|
export const manageVncSshTunnel: RequestHandler< |
||||||
|
unknown, |
||||||
|
{ forwardPort: number; protocol: string }, |
||||||
|
{ open: boolean; serverUuid: string } |
||||||
|
> = async (request, response) => { |
||||||
|
const { body: { open: rOpen, serverUuid: rServerUuid } = {} } = request; |
||||||
|
|
||||||
|
const isOpen = sanitize(rOpen, 'boolean'); |
||||||
|
const serverUuid = sanitize(rServerUuid, 'string'); |
||||||
|
|
||||||
|
try { |
||||||
|
assert( |
||||||
|
REP_UUID.test(serverUuid), |
||||||
|
`Server UUID must be a valid UUIDv4; got: [${serverUuid}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Assert input failed when manage VNC SSH tunnel; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
stdoutVar({ isOpen, serverUuid }, 'Manage VNC SSH tunnel params: '); |
||||||
|
|
||||||
|
let operation = 'close'; |
||||||
|
|
||||||
|
if (isOpen) { |
||||||
|
operation = 'open'; |
||||||
|
} |
||||||
|
|
||||||
|
let rsbody: { forwardPort: number; protocol: string }; |
||||||
|
|
||||||
|
try { |
||||||
|
rsbody = await vncpipe(serverUuid, isOpen); |
||||||
|
} catch (error) { |
||||||
|
stderr( |
||||||
|
`Failed to ${operation} VNC SSH tunnel to server ${serverUuid}; CAUSE: ${error}`, |
||||||
|
); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
return response.json(rsbody); |
||||||
|
}; |
@ -1,3 +0,0 @@ |
|||||||
import { buildHostPowerHandler } from './buildHostPowerHandler'; |
|
||||||
|
|
||||||
export const poweroffHost = buildHostPowerHandler('poweroff'); |
|
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const poweroffStriker = buildPowerHandler('poweroff'); |
@ -1,3 +0,0 @@ |
|||||||
import { buildHostPowerHandler } from './buildHostPowerHandler'; |
|
||||||
|
|
||||||
export const rebootHost = buildHostPowerHandler('reboot'); |
|
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const rebootStriker = buildPowerHandler('reboot'); |
@ -0,0 +1,70 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { LOCAL, REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { variable } from '../../accessModule'; |
||||||
|
import { toHostUUID } from '../../convertHostUUID'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, stdoutVar } from '../../shell'; |
||||||
|
|
||||||
|
export const setMapNetwork: RequestHandler< |
||||||
|
{ uuid: string }, |
||||||
|
undefined, |
||||||
|
SetMapNetworkRequestBody |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { value: rValue } = {}, |
||||||
|
params: { uuid: rHostUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const value = sanitize(rValue, 'number'); |
||||||
|
|
||||||
|
let hostUuid = sanitize(rHostUuid, 'string', { fallback: LOCAL }); |
||||||
|
|
||||||
|
hostUuid = toHostUUID(hostUuid); |
||||||
|
|
||||||
|
stdoutVar({ hostUuid, value }, `Set map network variable with: `); |
||||||
|
|
||||||
|
try { |
||||||
|
assert( |
||||||
|
value in [0, 1], |
||||||
|
`Variable value must be a number boolean (0 or 1); got [${value}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(hostUuid), |
||||||
|
`Host UUID must be a valid UUIDv4; got [${hostUuid}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Assert failed when set map network variable; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
const result = await variable({ |
||||||
|
file: __filename, |
||||||
|
variable_default: 0, |
||||||
|
varaible_description: 'striker_0202', |
||||||
|
variable_name: 'config::map_network', |
||||||
|
variable_section: 'config', |
||||||
|
variable_source_table: 'hosts', |
||||||
|
variable_source_uuid: hostUuid, |
||||||
|
variable_value: value, |
||||||
|
}); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(result), |
||||||
|
`Result must be UUID of modified record; got: [${result}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr( |
||||||
|
`Failed to set map network variable for host ${hostUuid}; CAUSE: ${error}`, |
||||||
|
); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(204).send(); |
||||||
|
}; |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildAnPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const startAn = buildAnPowerHandler('start'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const startServer = buildPowerHandler('startserver'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const startSubnode = buildPowerHandler('start'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildAnPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const stopAn = buildAnPowerHandler('stop'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const stopServer = buildPowerHandler('stopserver'); |
@ -0,0 +1,3 @@ |
|||||||
|
import { buildPowerHandler } from './buildPowerHandler'; |
||||||
|
|
||||||
|
export const stopSubnode = buildPowerHandler('stop'); |
@ -0,0 +1,138 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_PEACEFUL_STRING } from '../../consts'; |
||||||
|
|
||||||
|
import { getFenceSpec, timestamp, write } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, stdoutVar, uuid } from '../../shell'; |
||||||
|
|
||||||
|
const handleNumberType = (v: unknown) => String(sanitize(v, 'number')); |
||||||
|
|
||||||
|
const handleStringType = (v: unknown) => sanitize(v, 'string'); |
||||||
|
|
||||||
|
const MAP_TO_VAR_TYPE: Record< |
||||||
|
AnvilDataFenceParameterType, |
||||||
|
(v: unknown) => string |
||||||
|
> = { |
||||||
|
boolean: (v) => (sanitize(v, 'boolean') ? '1' : ''), |
||||||
|
integer: handleNumberType, |
||||||
|
second: handleNumberType, |
||||||
|
select: handleStringType, |
||||||
|
string: handleStringType, |
||||||
|
}; |
||||||
|
|
||||||
|
export const createFence: RequestHandler< |
||||||
|
{ uuid?: string }, |
||||||
|
undefined, |
||||||
|
{ |
||||||
|
agent: string; |
||||||
|
name: string; |
||||||
|
parameters: { [parameterId: string]: string }; |
||||||
|
} |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { agent: rAgent, name: rName, parameters: rParameters }, |
||||||
|
params: { uuid: rUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
let fenceSpec: AnvilDataFenceHash; |
||||||
|
|
||||||
|
try { |
||||||
|
fenceSpec = await getFenceSpec(); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get fence devices specification; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const agent = sanitize(rAgent, 'string'); |
||||||
|
const name = sanitize(rName, 'string'); |
||||||
|
const fenceUuid = sanitize(rUuid, 'string', { fallback: uuid() }); |
||||||
|
|
||||||
|
const { [agent]: agentSpec } = fenceSpec; |
||||||
|
|
||||||
|
try { |
||||||
|
assert.ok(agentSpec, `Agent is unknown to spec; got [${agent}]`); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(name), |
||||||
|
`Name must be a peaceful string; got [${name}]`, |
||||||
|
); |
||||||
|
|
||||||
|
const rParamsType = typeof rParameters; |
||||||
|
|
||||||
|
assert( |
||||||
|
rParamsType === 'object', |
||||||
|
`Parameters must be an object; got type [${rParamsType}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
assert( |
||||||
|
`Failed to assert value when working with fence device; CAUSE: ${error}`, |
||||||
|
); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const { parameters: agentSpecParams } = agentSpec; |
||||||
|
|
||||||
|
const args = Object.entries(agentSpecParams) |
||||||
|
.reduce<string[]>((previous, [paramId, paramSpec]) => { |
||||||
|
const { content_type: paramType, default: paramDefault } = paramSpec; |
||||||
|
const { [paramId]: rParamValue } = rParameters; |
||||||
|
|
||||||
|
if ( |
||||||
|
[paramDefault, '', null, undefined].some((bad) => rParamValue === bad) |
||||||
|
) |
||||||
|
return previous; |
||||||
|
|
||||||
|
// TODO: add SQL modifier after finding a way to escape single quotes
|
||||||
|
const paramValue = MAP_TO_VAR_TYPE[paramType](rParamValue); |
||||||
|
|
||||||
|
previous.push(`${paramId}="${paramValue}"`); |
||||||
|
|
||||||
|
return previous; |
||||||
|
}, []) |
||||||
|
.join(' '); |
||||||
|
|
||||||
|
stdoutVar( |
||||||
|
{ agent, args, name }, |
||||||
|
`Proceed to record fence device (${fenceUuid}): `, |
||||||
|
); |
||||||
|
|
||||||
|
const modifiedDate = timestamp(); |
||||||
|
|
||||||
|
try { |
||||||
|
const wcode = await write( |
||||||
|
`INSERT INTO
|
||||||
|
fences ( |
||||||
|
fence_uuid, |
||||||
|
fence_name, |
||||||
|
fence_agent, |
||||||
|
fence_arguments, |
||||||
|
modified_date |
||||||
|
) VALUES ( |
||||||
|
'${fenceUuid}', |
||||||
|
'${name}', |
||||||
|
'${agent}', |
||||||
|
'${args}', |
||||||
|
'${modifiedDate}' |
||||||
|
) ON CONFLICT (fence_uuid) |
||||||
|
DO UPDATE SET |
||||||
|
fence_name = '${name}', |
||||||
|
fence_agent = '${agent}', |
||||||
|
fence_arguments = '${args}', |
||||||
|
modified_date = '${modifiedDate}';`,
|
||||||
|
); |
||||||
|
|
||||||
|
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to write fence record; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const scode = rUuid ? 200 : 201; |
||||||
|
|
||||||
|
return response.status(scode).send(); |
||||||
|
}; |
@ -0,0 +1,20 @@ |
|||||||
|
import { DELETED } from '../../consts'; |
||||||
|
|
||||||
|
import { write } from '../../accessModule'; |
||||||
|
import { buildDeleteRequestHandler } from '../buildDeleteRequestHandler'; |
||||||
|
import join from '../../join'; |
||||||
|
|
||||||
|
export const deleteFence = buildDeleteRequestHandler({ |
||||||
|
delete: async (fenceUuids) => { |
||||||
|
const wcode = await write( |
||||||
|
`UPDATE fences
|
||||||
|
SET fence_arguments = '${DELETED}' |
||||||
|
WHERE fence_uuid IN (${join(fenceUuids, { |
||||||
|
elementWrapper: "'", |
||||||
|
separator: ',', |
||||||
|
})});`,
|
||||||
|
); |
||||||
|
|
||||||
|
if (wcode !== 0) throw Error(`Write exited with code ${wcode}`); |
||||||
|
}, |
||||||
|
}); |
@ -1,2 +1,5 @@ |
|||||||
|
export * from './createFence'; |
||||||
|
export * from './deleteFence'; |
||||||
export * from './getFence'; |
export * from './getFence'; |
||||||
export * from './getFenceTemplate'; |
export * from './getFenceTemplate'; |
||||||
|
export * from './updateFence'; |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
import { createFence } from './createFence'; |
||||||
|
|
||||||
|
export const updateFence = createFence; |
@ -1,8 +1,5 @@ |
|||||||
import { RequestHandler } from 'express'; |
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
import { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
export const createHost: RequestHandler = (request, response) => { |
||||||
import { configStriker } from './configStriker'; |
return response.status(204).send(); |
||||||
|
}; |
||||||
export const createHost: RequestHandler = buildBranchRequestHandler({ |
|
||||||
striker: configStriker, |
|
||||||
}); |
|
||||||
|
@ -0,0 +1,154 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { |
||||||
|
REP_IPV4, |
||||||
|
REP_IPV4_CSV, |
||||||
|
REP_PEACEFUL_STRING, |
||||||
|
REP_UUID, |
||||||
|
SERVER_PATHS, |
||||||
|
} from '../../consts'; |
||||||
|
|
||||||
|
import { job, query, variable } from '../../accessModule'; |
||||||
|
import { buildJobData } from '../../buildJobData'; |
||||||
|
import { buildNetworkConfig } from '../../fconfig'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, stdoutVar } from '../../shell'; |
||||||
|
import { cvar } from '../../varn'; |
||||||
|
|
||||||
|
export const prepareNetwork: RequestHandler< |
||||||
|
UpdateHostParams, |
||||||
|
undefined, |
||||||
|
PrepareNetworkRequestBody |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { |
||||||
|
dns: rDns, |
||||||
|
gateway: rGateway, |
||||||
|
hostName: rHostName, |
||||||
|
gatewayInterface: rGatewayInterface, |
||||||
|
networks = [], |
||||||
|
} = {}, |
||||||
|
params: { hostUUID }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const dns = sanitize(rDns, 'string'); |
||||||
|
const gateway = sanitize(rGateway, 'string'); |
||||||
|
const hostName = sanitize(rHostName, 'string'); |
||||||
|
const gatewayInterface = sanitize(rGatewayInterface, 'string'); |
||||||
|
|
||||||
|
try { |
||||||
|
assert( |
||||||
|
REP_UUID.test(hostUUID), |
||||||
|
`Host UUID must be a valid UUIDv4; got [${hostUUID}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_IPV4_CSV.test(dns), |
||||||
|
`DNS must be a valid IPv4 CSV; got [${dns}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_IPV4.test(gateway), |
||||||
|
`Gateway must be a valid IPv4; got [${gateway}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(hostName), |
||||||
|
`Host name must be a peaceful string; got [${hostName}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(gatewayInterface), |
||||||
|
`Gateway interface must be a peaceful string; got [${gatewayInterface}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to assert value when prepare network; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let hostType: string; |
||||||
|
|
||||||
|
try { |
||||||
|
const rows = await query<[[string]]>( |
||||||
|
`SELECT host_type FROM hosts WHERE host_uuid = '${hostUUID}';`, |
||||||
|
); |
||||||
|
|
||||||
|
assert.ok(rows.length, `No record found`); |
||||||
|
|
||||||
|
[[hostType]] = rows; |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get host type with ${hostUUID}; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
networks.forEach((network) => { |
||||||
|
const { interfaces: ifaces, type } = network; |
||||||
|
|
||||||
|
if ( |
||||||
|
hostType === 'node' && |
||||||
|
['bcn', 'ifn'].includes(type) && |
||||||
|
ifaces.length === 2 && |
||||||
|
!ifaces.some((iface) => !iface) |
||||||
|
) { |
||||||
|
network.createBridge = '1'; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
const configData: FormConfigData = { |
||||||
|
[cvar(2, 'dns')]: { step: 2, value: dns }, |
||||||
|
[cvar(2, 'gateway')]: { step: 2, value: gateway }, |
||||||
|
[cvar(2, 'gateway_interface')]: { step: 2, value: gatewayInterface }, |
||||||
|
[cvar(2, 'host_name')]: { step: 2, value: hostName }, |
||||||
|
...buildNetworkConfig(networks), |
||||||
|
}; |
||||||
|
|
||||||
|
stdoutVar( |
||||||
|
configData, |
||||||
|
`Config data before prepare network on host ${hostUUID}: `, |
||||||
|
); |
||||||
|
|
||||||
|
const configEntries = Object.entries(configData); |
||||||
|
|
||||||
|
try { |
||||||
|
for (const [ckey, cdetail] of configEntries) { |
||||||
|
const { step = 1, value } = cdetail; |
||||||
|
|
||||||
|
const vuuid = await variable({ |
||||||
|
file: __filename, |
||||||
|
variable_default: '', |
||||||
|
varaible_description: '', |
||||||
|
variable_name: ckey, |
||||||
|
variable_section: `config_step${step}`, |
||||||
|
variable_source_uuid: hostUUID, |
||||||
|
variable_source_table: 'hosts', |
||||||
|
variable_value: value, |
||||||
|
}); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(vuuid), |
||||||
|
`Not a UUIDv4 post insert or update of ${ckey} with [${cdetail}]`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
await job({ |
||||||
|
file: __filename, |
||||||
|
job_command: SERVER_PATHS.usr.sbin['anvil-configure-host'].self, |
||||||
|
job_data: buildJobData({ |
||||||
|
entries: configEntries, |
||||||
|
getValue: ({ value }) => String(value), |
||||||
|
}), |
||||||
|
job_name: 'configure::network', |
||||||
|
job_title: 'job_0001', |
||||||
|
job_description: 'job_0071', |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to queue prepare network; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
return response.send(); |
||||||
|
}; |
@ -1,8 +1,12 @@ |
|||||||
import { RequestHandler } from 'express'; |
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
import { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
import { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
||||||
|
import { configStriker } from './configStriker'; |
||||||
|
import { prepareNetwork } from './prepareNetwork'; |
||||||
import { setHostInstallTarget } from './setHostInstallTarget'; |
import { setHostInstallTarget } from './setHostInstallTarget'; |
||||||
|
|
||||||
export const updateHost: RequestHandler = buildBranchRequestHandler({ |
export const updateHost: RequestHandler = buildBranchRequestHandler({ |
||||||
'install-target': setHostInstallTarget, |
'install-target': setHostInstallTarget as RequestHandler, |
||||||
|
'subnode-network': prepareNetwork as RequestHandler, |
||||||
|
striker: configStriker, |
||||||
}); |
}); |
||||||
|
@ -0,0 +1,66 @@ |
|||||||
|
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, stdoutVar } from '../../shell'; |
||||||
|
|
||||||
|
export const deleteServer: RequestHandler< |
||||||
|
{ serverUuid?: string }, |
||||||
|
undefined, |
||||||
|
{ serverUuids: string[] } |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { serverUuids: rServerUuids } = {}, |
||||||
|
params: { serverUuid: rServerUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const serverUuids = sanitize(rServerUuids, 'string[]', { |
||||||
|
modifierType: 'sql', |
||||||
|
}); |
||||||
|
|
||||||
|
if (rServerUuid) { |
||||||
|
serverUuids.push( |
||||||
|
sanitize(rServerUuid, 'string', { |
||||||
|
modifierType: 'sql', |
||||||
|
}), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
stdoutVar(serverUuids, `Delete servers with: `); |
||||||
|
|
||||||
|
for (const serverUuid of serverUuids) { |
||||||
|
try { |
||||||
|
assert( |
||||||
|
REP_UUID.test(serverUuid), |
||||||
|
`Server UUID must be a valid UUIDv4; got [${serverUuid}]`, |
||||||
|
); |
||||||
|
|
||||||
|
const rows: [[string]] = await query( |
||||||
|
`SELECT server_host_uuid FROM servers WHERE server_uuid = '${serverUuid}';`, |
||||||
|
); |
||||||
|
|
||||||
|
assert.ok(rows.length, `Server ${serverUuid} not found`); |
||||||
|
|
||||||
|
const [[serverHostUuid]] = rows; |
||||||
|
|
||||||
|
job({ |
||||||
|
file: __filename, |
||||||
|
job_command: `${SERVER_PATHS.usr.sbin['anvil-delete-server'].self}`, |
||||||
|
job_data: `server_uuid=${serverUuid}`, |
||||||
|
job_description: 'job_0209', |
||||||
|
job_host_uuid: serverHostUuid, |
||||||
|
job_name: 'server::delete', |
||||||
|
job_title: 'job_0208', |
||||||
|
}); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to initiate delete server ${serverUuid}; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(204).send(); |
||||||
|
}; |
@ -1,3 +1,4 @@ |
|||||||
export { createServer } from './createServer'; |
export * from './createServer'; |
||||||
export { getServer } from './getServer'; |
export * from './deleteServer'; |
||||||
export { getServerDetail } from './getServerDetail'; |
export * from './getServer'; |
||||||
|
export * from './getServerDetail'; |
||||||
|
@ -0,0 +1,86 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_IPV4, REP_PEACEFUL_STRING, REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { timestamp, write } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, uuid } from '../../shell'; |
||||||
|
|
||||||
|
export const createUps: RequestHandler< |
||||||
|
{ uuid?: string }, |
||||||
|
undefined, |
||||||
|
{ agent: string; ipAddress: string; name: string } |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { agent: rAgent, ipAddress: rIpAddress, name: rName } = {}, |
||||||
|
params: { uuid: rUuid }, |
||||||
|
} = request; |
||||||
|
|
||||||
|
const agent = sanitize(rAgent, 'string'); |
||||||
|
const ipAddress = sanitize(rIpAddress, 'string'); |
||||||
|
const name = sanitize(rName, 'string'); |
||||||
|
const upsUuid = sanitize(rUuid, 'string', { fallback: uuid() }); |
||||||
|
|
||||||
|
try { |
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(agent), |
||||||
|
`Agent must be a peaceful string; got [${agent}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_IPV4.test(ipAddress), |
||||||
|
`IP address must be a valid IPv4; got [${ipAddress}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(name), |
||||||
|
`Name must be a peaceful string; got [${name}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(upsUuid), |
||||||
|
`UPS UUID must be a valid UUIDv4; got [${upsUuid}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Assert value failed when working with UPS; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const modifiedDate = timestamp(); |
||||||
|
|
||||||
|
try { |
||||||
|
const wcode = await write( |
||||||
|
`INSERT INTO
|
||||||
|
upses ( |
||||||
|
ups_uuid, |
||||||
|
ups_name, |
||||||
|
ups_agent, |
||||||
|
ups_ip_address, |
||||||
|
modified_date |
||||||
|
) VALUES ( |
||||||
|
'${upsUuid}', |
||||||
|
'${name}', |
||||||
|
'${agent}', |
||||||
|
'${ipAddress}', |
||||||
|
'${modifiedDate}' |
||||||
|
) ON CONFLICT (ups_uuid) |
||||||
|
DO UPDATE SET |
||||||
|
ups_name = '${name}', |
||||||
|
ups_agent = '${agent}', |
||||||
|
ups_ip_address = '${ipAddress}', |
||||||
|
modified_date = '${modifiedDate}';`,
|
||||||
|
); |
||||||
|
|
||||||
|
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to write UPS record; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const scode = rUuid ? 200 : 201; |
||||||
|
|
||||||
|
return response.status(scode).send(); |
||||||
|
}; |
@ -0,0 +1,20 @@ |
|||||||
|
import { DELETED } from '../../consts'; |
||||||
|
|
||||||
|
import { write } from '../../accessModule'; |
||||||
|
import { buildDeleteRequestHandler } from '../buildDeleteRequestHandler'; |
||||||
|
import join from '../../join'; |
||||||
|
|
||||||
|
export const deleteUps = buildDeleteRequestHandler({ |
||||||
|
delete: async (upsUuids) => { |
||||||
|
const wcode = await write( |
||||||
|
`UPDATE upses
|
||||||
|
SET ups_ip_address = '${DELETED}' |
||||||
|
WHERE ups_uuid IN (${join(upsUuids, { |
||||||
|
elementWrapper: "'", |
||||||
|
separator: ',', |
||||||
|
})});`,
|
||||||
|
); |
||||||
|
|
||||||
|
if (wcode !== 0) throw Error(`Write exited with code ${wcode}`); |
||||||
|
}, |
||||||
|
}); |
@ -1,2 +1,5 @@ |
|||||||
|
export * from './createUps'; |
||||||
|
export * from './deleteUps'; |
||||||
export * from './getUPS'; |
export * from './getUPS'; |
||||||
export * from './getUPSTemplate'; |
export * from './getUPSTemplate'; |
||||||
|
export * from './updateUps'; |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
import { createUps } from './createUps'; |
||||||
|
|
||||||
|
export const updateUps = createUps; |
@ -0,0 +1,71 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { DELETED, REP_PEACEFUL_STRING, REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { insertOrUpdateUser, query } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { openssl, stderr, stdoutVar } from '../../shell'; |
||||||
|
|
||||||
|
export const createUser: RequestHandler< |
||||||
|
unknown, |
||||||
|
CreateUserResponseBody, |
||||||
|
CreateUserRequestBody |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { password: rPassword, userName: rUserName } = {}, |
||||||
|
user: { name: sessionUserName } = {}, |
||||||
|
} = request; |
||||||
|
|
||||||
|
if (sessionUserName !== 'admin') return response.status(401).send(); |
||||||
|
|
||||||
|
const password = sanitize(rPassword, 'string', { |
||||||
|
fallback: openssl('rand', '-base64', '12').trim().replaceAll('/', '!'), |
||||||
|
}); |
||||||
|
const userName = sanitize(rUserName, 'string', { modifierType: 'sql' }); |
||||||
|
|
||||||
|
stdoutVar({ password, userName }, 'Create user with params: '); |
||||||
|
|
||||||
|
try { |
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(password), |
||||||
|
`Password must be a peaceful string; got: [${password}]`, |
||||||
|
); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(userName), |
||||||
|
`User name must be a peaceful string; got: [${userName}]`, |
||||||
|
); |
||||||
|
|
||||||
|
const [[userCount]]: [[number]] = await query( |
||||||
|
`SELECT COUNT(user_uuid)
|
||||||
|
FROM users |
||||||
|
WHERE user_algorithm != '${DELETED}' AND user_name = '${userName}';`,
|
||||||
|
); |
||||||
|
|
||||||
|
assert(userCount === 0, `User name [${userName}] already used`); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to assert value when creating user; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
const result = await insertOrUpdateUser({ |
||||||
|
file: __filename, |
||||||
|
user_name: userName, |
||||||
|
user_password_hash: password, |
||||||
|
}); |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(result), |
||||||
|
`Insert or update failed with result [${result}]`, |
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to record user to database; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
return response.status(201).send({ password }); |
||||||
|
}; |
@ -1,2 +1,4 @@ |
|||||||
|
export * from './createUser'; |
||||||
export * from './deleteUser'; |
export * from './deleteUser'; |
||||||
export * from './getUser'; |
export * from './getUser'; |
||||||
|
export * from './updateUser'; |
||||||
|
@ -0,0 +1,140 @@ |
|||||||
|
import assert from 'assert'; |
||||||
|
import { RequestHandler } from 'express'; |
||||||
|
|
||||||
|
import { REP_PEACEFUL_STRING, REP_UUID } from '../../consts'; |
||||||
|
|
||||||
|
import { encrypt, query, write } from '../../accessModule'; |
||||||
|
import { sanitize } from '../../sanitize'; |
||||||
|
import { stderr, stdoutVar } from '../../shell'; |
||||||
|
|
||||||
|
export const updateUser: RequestHandler< |
||||||
|
UserParamsDictionary, |
||||||
|
undefined, |
||||||
|
UpdateUserRequestBody |
||||||
|
> = async (request, response) => { |
||||||
|
const { |
||||||
|
body: { password: rPassword, userName: rUserName } = {}, |
||||||
|
params: { userUuid }, |
||||||
|
user: { name: sessionUserName, uuid: sessionUserUuid } = {}, |
||||||
|
} = request; |
||||||
|
|
||||||
|
if (sessionUserName !== 'admin' && userUuid !== sessionUserUuid) |
||||||
|
return response.status(401).send(); |
||||||
|
|
||||||
|
const password = sanitize(rPassword, 'string'); |
||||||
|
const userName = sanitize(rUserName, 'string', { modifierType: 'sql' }); |
||||||
|
|
||||||
|
stdoutVar({ password, userName }, `Update user ${userUuid} with params: `); |
||||||
|
|
||||||
|
try { |
||||||
|
if (password.length) { |
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(password), |
||||||
|
`Password must be a valid peaceful string; got: [${password}]`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if (userName.length) { |
||||||
|
assert( |
||||||
|
REP_PEACEFUL_STRING.test(userName), |
||||||
|
`User name must be a peaceful string; got: [${userName}]`, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
assert( |
||||||
|
REP_UUID.test(userUuid), |
||||||
|
`User UUID must be a valid UUIDv4; got: [${userUuid}]`, |
||||||
|
); |
||||||
|
|
||||||
|
const [[existingUserName]]: [[string]] = await query( |
||||||
|
`SELECT user_name FROM users WHERE user_uuid = '${userUuid}';`, |
||||||
|
); |
||||||
|
|
||||||
|
assert(existingUserName !== 'admin' || userName, 'Cannot '); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Assert failed when update user; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(400).send(); |
||||||
|
} |
||||||
|
|
||||||
|
let existingUser: [ |
||||||
|
[ |
||||||
|
user_name: string, |
||||||
|
user_password_hash: string, |
||||||
|
user_salt: string, |
||||||
|
user_algorithm: string, |
||||||
|
user_hash_count: string, |
||||||
|
], |
||||||
|
]; |
||||||
|
|
||||||
|
try { |
||||||
|
existingUser = await query( |
||||||
|
`SELECT
|
||||||
|
user_name, |
||||||
|
user_password_hash, |
||||||
|
user_salt, |
||||||
|
user_algorithm, |
||||||
|
user_hash_count |
||||||
|
FROM users |
||||||
|
WHERE user_uuid = '${userUuid}' |
||||||
|
ORDER BY modified_date DESC |
||||||
|
LIMIT 1;`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to find existing user ${userUuid}; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
if (existingUser.length !== 1) { |
||||||
|
return response.status(404).send(); |
||||||
|
} |
||||||
|
|
||||||
|
const [[xUserName, xPasswordHash, xSalt, xAlgorithm, xHashCount]] = |
||||||
|
existingUser; |
||||||
|
|
||||||
|
const assigns: string[] = []; |
||||||
|
|
||||||
|
if (password.length) { |
||||||
|
let passwordHash: string; |
||||||
|
|
||||||
|
try { |
||||||
|
({ user_password_hash: passwordHash } = await encrypt({ |
||||||
|
algorithm: xAlgorithm, |
||||||
|
hash_count: xHashCount, |
||||||
|
password, |
||||||
|
salt: xSalt, |
||||||
|
})); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Encrypt failed when update user; CAUSE ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
if (passwordHash !== xPasswordHash) { |
||||||
|
assigns.push(`user_password_hash = '${passwordHash}'`); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (userName.length && xUserName !== 'admin' && userName !== xUserName) { |
||||||
|
assigns.push(`user_name = '${userName}'`); |
||||||
|
} |
||||||
|
|
||||||
|
if (assigns.length) { |
||||||
|
try { |
||||||
|
const wcode = await write( |
||||||
|
`UPDATE users SET ${assigns.join( |
||||||
|
',', |
||||||
|
)} WHERE user_uuid = '${userUuid}';`,
|
||||||
|
); |
||||||
|
|
||||||
|
assert(wcode === 0, `Update users failed with code: ${wcode}`); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to record user changes to database; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return response.send(); |
||||||
|
}; |
@ -0,0 +1,2 @@ |
|||||||
|
export const cvar = (step: number, name: string) => |
||||||
|
['form', `config_step${step}`, name, 'value'].join('::'); |
@ -0,0 +1,55 @@ |
|||||||
|
import { Handler } from 'express'; |
||||||
|
|
||||||
|
import { LOCAL } from '../lib/consts'; |
||||||
|
|
||||||
|
import { query } from '../lib/accessModule'; |
||||||
|
import { toHostUUID } from '../lib/convertHostUUID'; |
||||||
|
import { stderr, stdoutVar } from '../lib/shell'; |
||||||
|
|
||||||
|
export const assertInit = |
||||||
|
({ |
||||||
|
fail = (rq, rs) => rs.status(401).send(), |
||||||
|
hostUuid: rHostUuid = LOCAL, |
||||||
|
invert, |
||||||
|
succeed = (rq, rs, nx) => nx(), |
||||||
|
}: { |
||||||
|
fail?: (...args: Parameters<Handler>) => void; |
||||||
|
hostUuid?: string; |
||||||
|
invert?: boolean; |
||||||
|
succeed?: (...args: Parameters<Handler>) => void; |
||||||
|
} = {}): Handler => |
||||||
|
async (...args) => { |
||||||
|
const { 1: response } = args; |
||||||
|
const hostUuid = toHostUUID(rHostUuid); |
||||||
|
|
||||||
|
let rows: [[string]]; |
||||||
|
|
||||||
|
try { |
||||||
|
rows = await query( |
||||||
|
`SELECT variable_value
|
||||||
|
FROM variables |
||||||
|
WHERE variable_name = 'system::configured' |
||||||
|
AND variable_source_table = 'hosts' |
||||||
|
AND variable_source_uuid = '${hostUuid}' |
||||||
|
LIMIT 1;`,
|
||||||
|
); |
||||||
|
} catch (error) { |
||||||
|
stderr(`Failed to get system configured flag; CAUSE: ${error}`); |
||||||
|
|
||||||
|
return response.status(500).send(); |
||||||
|
} |
||||||
|
|
||||||
|
stdoutVar(rows, `Configured variable of host ${hostUuid}: `); |
||||||
|
|
||||||
|
let condition = rows.length === 1 && rows[0][0] === '1'; |
||||||
|
|
||||||
|
if (invert) condition = !condition; |
||||||
|
|
||||||
|
if (condition) { |
||||||
|
stderr(`Assert init failed; invert=${invert}`); |
||||||
|
|
||||||
|
return fail(...args); |
||||||
|
} |
||||||
|
|
||||||
|
return succeed(...args); |
||||||
|
}; |
@ -0,0 +1,7 @@ |
|||||||
|
import passport from './passport'; |
||||||
|
import session from './session'; |
||||||
|
|
||||||
|
export * from './assertAuthentication'; |
||||||
|
export * from './assertInit'; |
||||||
|
|
||||||
|
export { passport, session }; |
@ -1,9 +1,24 @@ |
|||||||
import express from 'express'; |
import express from 'express'; |
||||||
|
|
||||||
import getAnvil from '../lib/request_handlers/anvil/getAnvil'; |
import { |
||||||
|
getAnvil, |
||||||
|
getAnvilCpu, |
||||||
|
getAnvilSummary, |
||||||
|
getAnvilDetail, |
||||||
|
getAnvilMemory, |
||||||
|
getAnvilNetwork, |
||||||
|
getAnvilStore, |
||||||
|
} from '../lib/request_handlers/anvil'; |
||||||
|
|
||||||
const router = express.Router(); |
const router = express.Router(); |
||||||
|
|
||||||
router.get('/', getAnvil); |
router |
||||||
|
.get('/', getAnvil) |
||||||
|
.get('/summary', getAnvilSummary) |
||||||
|
.get('/:anvilUuid/cpu', getAnvilCpu) |
||||||
|
.get('/:anvilUuid/memory', getAnvilMemory) |
||||||
|
.get('/:anvilUuid/network', getAnvilNetwork) |
||||||
|
.get('/:anvilUuid/store', getAnvilStore) |
||||||
|
.get('/:anvilUuid', getAnvilDetail); |
||||||
|
|
||||||
export default router; |
export default router; |
||||||
|
@ -1,9 +1,20 @@ |
|||||||
import express from 'express'; |
import express from 'express'; |
||||||
|
|
||||||
import { getFence, getFenceTemplate } from '../lib/request_handlers/fence'; |
import { |
||||||
|
createFence, |
||||||
|
deleteFence, |
||||||
|
getFence, |
||||||
|
getFenceTemplate, |
||||||
|
updateFence, |
||||||
|
} from '../lib/request_handlers/fence'; |
||||||
|
|
||||||
const router = express.Router(); |
const router = express.Router(); |
||||||
|
|
||||||
router.get('/', getFence).get('/template', getFenceTemplate); |
router |
||||||
|
.delete('/:uuid?', deleteFence) |
||||||
|
.get('/', getFence) |
||||||
|
.get('/template', getFenceTemplate) |
||||||
|
.post('/', createFence) |
||||||
|
.put('/:uuid', updateFence); |
||||||
|
|
||||||
export default router; |
export default router; |
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue