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
|
||||
const NODE_AND_DR_RESERVED_MEMORY_SIZE = 8589934592; |
||||
|
||||
export default NODE_AND_DR_RESERVED_MEMORY_SIZE; |
||||
export const NODE_AND_DR_RESERVED_MEMORY_SIZE = 8589934592; |
||||
|
@ -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 { cname } from '../../cname'; |
||||
import { stdout } from '../../shell'; |
||||
|
||||
export const logout: RequestHandler = (request, response) => { |
||||
request.session.destroy((error) => { |
||||
let scode = 204; |
||||
|
||||
if (error) { |
||||
scode = 500; |
||||
|
||||
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 './poweroffHost'; |
||||
export * from './rebootHost'; |
||||
export * from './joinAn'; |
||||
export * from './leaveAn'; |
||||
export * from './manageVncSshTunnel'; |
||||
export * from './poweroffStriker'; |
||||
export * from './rebootStriker'; |
||||
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'; |
||||
|
@ -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 './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 { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
||||
import { configStriker } from './configStriker'; |
||||
|
||||
export const createHost: RequestHandler = buildBranchRequestHandler({ |
||||
striker: configStriker, |
||||
}); |
||||
export const createHost: RequestHandler = (request, response) => { |
||||
return response.status(204).send(); |
||||
}; |
||||
|
@ -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 { buildBranchRequestHandler } from '../buildBranchRequestHandler'; |
||||
import { configStriker } from './configStriker'; |
||||
import { prepareNetwork } from './prepareNetwork'; |
||||
import { setHostInstallTarget } from './setHostInstallTarget'; |
||||
|
||||
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 { getServer } from './getServer'; |
||||
export { getServerDetail } from './getServerDetail'; |
||||
export * from './createServer'; |
||||
export * from './deleteServer'; |
||||
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 './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 './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 getAnvil from '../lib/request_handlers/anvil/getAnvil'; |
||||
import { |
||||
getAnvil, |
||||
getAnvilCpu, |
||||
getAnvilSummary, |
||||
getAnvilDetail, |
||||
getAnvilMemory, |
||||
getAnvilNetwork, |
||||
getAnvilStore, |
||||
} from '../lib/request_handlers/anvil'; |
||||
|
||||
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; |
||||
|
@ -1,9 +1,20 @@ |
||||
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(); |
||||
|
||||
router.get('/', getFence).get('/template', getFenceTemplate); |
||||
router |
||||
.delete('/:uuid?', deleteFence) |
||||
.get('/', getFence) |
||||
.get('/template', getFenceTemplate) |
||||
.post('/', createFence) |
||||
.put('/:uuid', updateFence); |
||||
|
||||
export default router; |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue