From 9c8b03506a6f4b850f08e7d88aae597d3ba8ce0e Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Tue, 22 Jun 2021 18:50:06 +0000 Subject: [server/registration/idoit] Rework to new hw-collection method + restruce of the idoit external backend --- server/api/registration.js | 298 +++++++++++++++++++++++++++------------------ 1 file changed, 181 insertions(+), 117 deletions(-) (limited to 'server/api') diff --git a/server/api/registration.js b/server/api/registration.js index 4897bb5..fd8b808 100644 --- a/server/api/registration.js +++ b/server/api/registration.js @@ -14,6 +14,7 @@ const log = require(path.join(__appdir, 'lib', 'log')) const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse')) // This is needed for parsing vendor/product codes const pci = require(path.join(__appdir, 'lib', 'pci')) +const EdidReader = require('edid-reader') // Permission check middleware router.all(['', '/hooks', '/:y', '/hooks/:x'], async (req, res, next) => { @@ -183,134 +184,144 @@ noAuthRouter.post('/group', (req, res) => { * Add method for adding a client or server. */ noAuthRouter.postAsync('/clients', async (req, res) => { - let client = req.body.client - if (typeof client === 'string') client = JSON.parse(client) + let client = {} + + // Defines weather the answer is a ipxe script or json response let ipxe = req.body.ipxe if (typeof ipxe === 'string' && ipxe === 'true') ipxe = true - let automatic = req.body.automatic - if (typeof automatic === 'string' && automatic === 'true') automatic = true - let confirmation = req.body.confirmation - if (typeof confirmation === 'string' && confirmation === 'true') confirmation = true - - // If the client already exists return the configloader ipxe script. - const clientDb = await db.client.findOne({ where: { uuid: client.uuid } }) - if (clientDb) { - if (ipxe) return res.send(`#!ipxe\nchain https://` + url + `/api/configloader/\${uuid}`) - else return res.send({ error: 'CLIENT_ALREADY_EXISTS', msg: 'A client with the provided UUID does already exist.' }) - } - - if (!client.type) client.type = 'CLIENT' - - // DHCP network stuff: - - // TODO: Multiip / backend problems - // * Multiple backends possible ? multiple dhcp's? if set ip differentiates which one should we save in the bas db? - // * Only servers have multiple ips? No multi leased ips possible? - - // If there is no ip, we don't need DHCP checks. - // Only the first ip address is checked! client.networks[0] let dhcp = false - if (client.networks.length >= 1 && client.type === 'CLIENT') { - const network = client.networks[0] - // Get the dhcp backend. Only one dhcp backend can exist else -> conflict. - dhcp = await backendHelper.getDhcp() - let ipSelection = false - let setIpError - if (dhcp) { - if (automatic) { - // Set the name of the client if it's not set - if (!client.name) { - // Check the ip state in the dhcp - const ipCheck = await dhcp.instance.checkIp(dhcp.backend.credentials, network.ip) - // If it's not leased, set the hostname as clientname - if (!ipCheck.error && !ipCheck.leased && ipCheck.name !== '') { - if (ipCheck.name) client.name = ipCheck.name - if (ipCheck.id) dhcp.ref = ipCheck.id - } else { - client.name = client.type + '_' + client.uuid - const setIp = await dhcp.instance.setIp(dhcp.backend.credentials, network.ip, undefined, network.mac, undefined, true) - // Check for errors. - if (!setIp.error) { - dhcp.ref = setIp.ref - // Client ip set successfully - client.networks[0].ip = setIp.ip + if (req.body.version && req.body.version >= 2) { + /* New hardware collection script */ + client = await parseHardwareInformation(req.body) + } else { + client = req.body.client + if (typeof client === 'string') client = JSON.parse(client) + let automatic = req.body.automatic + if (typeof automatic === 'string' && automatic === 'true') automatic = true + let confirmation = req.body.confirmation + if (typeof confirmation === 'string' && confirmation === 'true') confirmation = true + + // If the client already exists return the configloader ipxe script. + const clientDb = await db.client.findOne({ where: { uuid: client.uuid } }) + if (clientDb) { + if (ipxe) return res.send(`#!ipxe\nchain https://` + url + `/api/configloader/\${uuid}`) + else return res.send({ error: 'CLIENT_ALREADY_EXISTS', msg: 'A client with the provided UUID does already exist.' }) + } + + // DHCP network stuff: + + // TODO: Multiip / backend problems + // * Multiple backends possible ? multiple dhcp's? if set ip differentiates which one should we save in the bas db? + // * Only servers have multiple ips? No multi leased ips possible? + + // If there is no ip, we don't need DHCP checks. + // Only the first ip address is checked! client.networks[0] + if (client.networks.length >= 1 && client.type === 'CLIENT') { + const network = client.networks[0] + // Get the dhcp backend. Only one dhcp backend can exist else -> conflict. + dhcp = await backendHelper.getDhcp() + let ipSelection = false + let setIpError + if (dhcp) { + if (automatic) { + // Set the name of the client if it's not set + if (!client.name) { + // Check the ip state in the dhcp + const ipCheck = await dhcp.instance.checkIp(dhcp.backend.credentials, network.ip) + + // If it's not leased, set the hostname as clientname + if (!ipCheck.error && !ipCheck.leased && ipCheck.name !== '') { + if (ipCheck.name) client.name = ipCheck.name + if (ipCheck.id) dhcp.ref = ipCheck.id } else { - log({ - category: 'ERROR_DHCP', - description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` - }) + client.name = client.type + '_' + client.uuid + const setIp = await dhcp.instance.setIp(dhcp.backend.credentials, network.ip, undefined, network.mac, undefined, true) + // Check for errors. + if (!setIp.error) { + dhcp.ref = setIp.ref + // Client ip set successfully + client.networks[0].ip = setIp.ip + } else { + log({ + category: 'ERROR_DHCP', + description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` + }) + } } } - } - } else if (network.dhcp) { - // If networks.dhcp is set the user already choose the ip and we have to set it now. - if (!network.dhcp.domain) { - // Check if there are multiple domains. - const domainList = await dhcp.instance.checkDomain(dhcp.backend.credentials) - if (domainList.length > 1) return res.send(buildSelectDomainIpxeMenu(client, domainList)) - else network.dhcp.domain = domainList[0] - } - if (!client.name) return res.send(buildNameClientIpxeMenu(client)) - if (confirmation) return res.send(buildOverviewIpxeMenu(client)) - - const setIp = await dhcp.instance.setIp(dhcp.backend.credentials, network.dhcp.ip, network.dhcp.domain, network.mac, client.name) - dhcp.ref = setIp.id - // Check for errors. - if (setIp.error) { - log({ - category: 'ERROR_DHCP', - description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` - }) - - // Setting the client ip failed - ipSelection = true - delete client.networks[0].dhcp - delete client.name - setIpError = setIp.msg + } else if (network.dhcp) { + // If networks.dhcp is set the user already choose the ip and we have to set it now. + if (!network.dhcp.domain) { + // Check if there are multiple domains. + const domainList = await dhcp.instance.checkDomain(dhcp.backend.credentials) + if (domainList.length > 1) return res.send(buildSelectDomainIpxeMenu(client, domainList)) + else network.dhcp.domain = domainList[0] + } + if (!client.name) return res.send(buildNameClientIpxeMenu(client)) + if (confirmation) return res.send(buildOverviewIpxeMenu(client)) + + const setIp = await dhcp.instance.setIp(dhcp.backend.credentials, network.dhcp.ip, network.dhcp.domain, network.mac, client.name) + dhcp.ref = setIp.id + // Check for errors. + if (setIp.error) { + log({ + category: 'ERROR_DHCP', + description: `[${dhcp.backend.id}] Error setting ip ${network.ip} for mac ${network.mac}\nError: ${setIp.msg}` + }) + + // Setting the client ip failed + ipSelection = true + delete client.networks[0].dhcp + delete client.name + setIpError = setIp.msg + } else { + // Client ip set successfully + client.networks[0].ip = network.dhcp.ip + client.networks[0].hostname = client.name + client.networks[0].domain = setIp.domain + } } else { - // Client ip set successfully - client.networks[0].ip = network.dhcp.ip - client.networks[0].hostname = client.name - client.networks[0].domain = setIp.domain + ipSelection = true } - } else { - ipSelection = true - } - if (ipSelection) { - // If not: check if the client has a leased ipv4 address. - const ipCheck = await dhcp.instance.checkIp(dhcp.backend.credentials, network.ip) + if (ipSelection) { + // If not: check if the client has a leased ipv4 address. + const ipCheck = await dhcp.instance.checkIp(dhcp.backend.credentials, network.ip) - // Build ipxe and return - if (ipxe && ipCheck.leased && !ipCheck.error) return res.send(buildSelectIpIpxeMenu(client, ipCheck.nextIps, setIpError)) - else if (!ipxe && ipCheck.leased && !ipCheck.error) return res.send({ client: client, ipList: ipCheck.nextIps }) + // Build ipxe and return + if (ipxe && ipCheck.leased && !ipCheck.error) return res.send(buildSelectIpIpxeMenu(client, ipCheck.nextIps, setIpError)) + else if (!ipxe && ipCheck.leased && !ipCheck.error) return res.send({ client: client, ipList: ipCheck.nextIps }) - // Set the hostname as clientname if it exists and is not a leased ip. - if (!ipCheck.leased && ipCheck.name) { - if (ipCheck.name) client.name = ipCheck.name - if (ipCheck.id) dhcp.ref = ipCheck.id - } else { - // Leased ip but no hostname? --> Maybe not waited long enough after DHCP deletion - let date = new Date() - const tenMin = 1000 * 60 * 10 - const fiveMin = 1000 * 60 * 5 - // Round up to the next 10-min mark for the error msg - // === Add 5 min to the time and round to the nearest one - const rounded = new Date(Math.round((date.getTime() + fiveMin) / tenMin) * tenMin) - - return res.send(buildNameClientIpxeMenu(client, ['Client has a fixed IP but NO hostname was found.', 'Infoblox might not be ready after client deletion.', `Wait until ${rounded.toTimeString()} or enter a name and continue anyways ...`])) + // Set the hostname as clientname if it exists and is not a leased ip. + if (!ipCheck.leased && ipCheck.name) { + if (ipCheck.name) client.name = ipCheck.name + if (ipCheck.id) dhcp.ref = ipCheck.id + } else { + // Leased ip but no hostname? --> Maybe not waited long enough after DHCP deletion + let date = new Date() + const tenMin = 1000 * 60 * 10 + const fiveMin = 1000 * 60 * 5 + // Round up to the next 10-min mark for the error msg + // === Add 5 min to the time and round to the nearest one + const rounded = new Date(Math.round((date.getTime() + fiveMin) / tenMin) * tenMin) + + return res.send(buildNameClientIpxeMenu(client, ['Client has a fixed IP but NO hostname was found.', 'Infoblox might not be ready after client deletion.', `Wait until ${rounded.toTimeString()} or enter a name and continue anyways ...`])) + } + } + } else { // End of DHCP Stuff + if (automatic) { + client.name = client.type + '_' + client.uuid } - } - } else { // End of DHCP Stuff - if (automatic) { - client.name = client.type + '_' + client.uuid } } } // Client does not exist. if (!client.parents) client.parents = [] + if (!client.name) client.name = client.type + '_' + client.uuid + if (!client.type) client.type = 'CLIENT' + // TODO: Save all IPs? Maybe only primary ip? const createClient = { name: client.name, description: client.type, ip: client.networks[0].ip, mac: client.networks[0].mac, uuid: client.uuid } if (client.type === 'CLIENT') createClient.registrationState = await getNextHookScript(client.parents) @@ -495,7 +506,7 @@ function getRecursiveParents (groupIds) { async function parseHardwareInformation (data) { let client = { 'parents': [], // TODO: - 'type': '', // SERVER OR CLIENT + 'type': data.type ? data.type : 'CLIENT', // SERVER OR CLIENT 'uuid': '', 'networks': [], // { 'mac': '', 'ip': '' } 'system': { @@ -509,8 +520,11 @@ async function parseHardwareInformation (data) { 'isEcc': false }, 'drives': [], - 'gpus': [] + 'gpus': [], + 'monitors': [], + 'contacts': [] } + if (data.name) client.name = data.name // TODO: Rack and Bay stuff @@ -641,9 +655,9 @@ async function parseHardwareInformation (data) { let network = { 'name': ip.ifname, 'mac': ip.address, - 'ip': '', - 'ipv6': '', - 'hostname': '' + 'ip': undefined, + 'ipv6': undefined, + 'hostname': undefined } for (let addr of ip['addr_info']) { @@ -671,6 +685,25 @@ async function parseHardwareInformation (data) { } /* edid */ + for (let rawEdid of data.edid) { + const edid = EdidReader.parse(rawEdid.edid) + client.monitors.push({ + model: edid.modelName, + vendor: edid.vendor, + serialnumber: edid.serialNumber, + modes: edid.standardDisplayModes, + port: rawEdid.path.split('/')[4].substring(6), + resolution: { + width: edid.dtds[0].horActivePixels, + height: edid.dtds[0].vertActivePixels + }, + dimensions: { + width: edid.displaySize[0], + height: edid.displaySize[1], + inch: Math.round(Math.sqrt(Math.pow(edid.displaySize[0], 2) + Math.pow(edid.displaySize[1], 2)) / 25.4) + } + }) + } /* lshw */ if (data.lshw && data.lshw.length > 0) { @@ -681,15 +714,46 @@ async function parseHardwareInformation (data) { client.gpus.push({ 'manufacturer': gpu.vendor, 'model': gpu.product - /*, - 'memory': , - 'unit': + /* + 'memory': undefined, + 'unit': undefined, */ }) } } } + /* Contacts */ + if (data.contacts && data.contacts.length > 0) { + for (let contact of data.contacts) { + client.contacts.push({ + 'firstname': contact.firstname, + 'lastname': contact.lastname + }) + } + } + + /* Location */ + if (data.location) { + // Server might get an name of the rack instead of an id + if (typeof data.location.parent === 'string' && isNaN(data.location.parent)) { + // Parent is not a number, so get the BAS ID for the object + const parent = await db.group.findOne({ where: { name: data.location.parent } }) + // findOne only returns the first object with the matching name, so if the name isn't unique id should be used + client.parents.push(parent.id) + } + + // Add bay and slot if given + if (data.location.bay || data.location.slot) { + client.location = { + ...(data.location.slot && { + slot: data.location.slot, + bay: data.location.bay ? data.location.bay : 0 + }) + } + } + } + return client } -- cgit v1.2.3-55-g7522