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 ++++-- server/lib/external-backends/backendhelper.js | 12 +- .../external-backends/backends/idoit-backend.js | 1113 ++++++++++++-------- server/package-lock.json | 10 + server/package.json | 1 + 5 files changed, 896 insertions(+), 538 deletions(-) (limited to 'server') 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 } diff --git a/server/lib/external-backends/backendhelper.js b/server/lib/external-backends/backendhelper.js index 0421cd7..e63f37f 100644 --- a/server/lib/external-backends/backendhelper.js +++ b/server/lib/external-backends/backendhelper.js @@ -31,7 +31,10 @@ async function addClient (client) { for (let element of elements) { conflict.createObject({ objectType: 'GROUP', objectId: element.id }) } - } else if (elements.length === 1) tmpClient['parentId'] = elements[0].backend_x_group.externalId + } + + // Nevertheless use the first one as "primary" parent + if (elements.length > 0) tmpClient['parentId'] = elements[0].backend_x_group.externalId } let addClient = await instance.addClient(backend.credentials, tmpClient) @@ -85,9 +88,12 @@ async function updateClient (client) { for (let element of elements) { conflict.createObject({ objectType: 'GROUP', objectId: element.id }) } - } else if (elements.length === 1) tmpClient['parentId'] = elements[0].backend_x_group.externalId - else if (elements.length === 0) tmpClient['parentId'] = null + } + + // Use first parent as parent + if (elements.length > 1) tmpClient['parentId'] = elements[0].backend_x_group.externalId } + try { let updateClient = await instance.updateClient(backend.credentials, tmpClient) updateClient.backendId = backend.id diff --git a/server/lib/external-backends/backends/idoit-backend.js b/server/lib/external-backends/backends/idoit-backend.js index 1d3ac6f..0a8b164 100644 --- a/server/lib/external-backends/backends/idoit-backend.js +++ b/server/lib/external-backends/backends/idoit-backend.js @@ -166,10 +166,7 @@ class IdoitBackend extends ExternalBackends { * * credentials: * The client parameters are all optional. - * client: { - * title: , parentId: , - * network: { mac: , ip: } - * } + * If given, the values will get updated after the client is createdö */ async addClient (credentials, client) { const c = this.mapCredentials(credentials) @@ -183,200 +180,14 @@ class IdoitBackend extends ExternalBackends { } client.parentId = parseInt(client.parentId) + // Hardcoded id from the Freiburg idoit, should be set in the dynamic backend settings if (client.type === 'CLIENT') { params['type'] = 10 - if (client.parentId) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId } } } else if (client.type === 'SERVER') { params['type'] = 5 - - if (client.location && client.location.bay === null) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId, 'option': client.location.assembly, 'insertion': client.location.insertion, 'pos': client.location.slot } } - if (client.formfactor) params.categories.C__CATG__FORMFACTOR = { 'data': { 'formfactor': client.formfactor.formfactor, 'rackunits': client.formfactor.rackunits } } - - // Rack segmentation - if (client.location.bay !== undefined && client.location.bay !== null) { - // Get all assigned objects of the rack (parentid) to check for existing rack segments at slot position. - const rackobjectsBody = this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__OBJECT' }, 'get_rack_objects') - const rackobjects = await this.axiosRequest(c.url, [rackobjectsBody], headers) - - // Get the name of the rack - console.log('') - console.log('Get Rack Name:') - const rackBody = this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': client.parentId, 'objID': client.parentId, 'category': 'C__CATG__GLOBAL' }, 'get_rack') - const rack = await this.axiosRequest(c.url, [rackBody], headers) - - if (!rack[0].result) return { error: 'IDOIT_ERROR', message: rack[0].error.message } - - const rackName = rack[0].result[0].title - - let objectPositionBodies = [] - - // For each segmentation object in the rack get the slot number - console.log('') - console.log('Get Slot IDs Request:') - for (let obj in rackobjects[0].result) { - const object = rackobjects[0].result[obj] - if (object.assigned_object.type !== 'C__OBJTYPE__RACK_SEGMENT') continue - objectPositionBodies.push(this.getBody('cmdb.category.read', { 'apikey': c.apikey, 'object': object.objID, 'objID': object.objID, 'category': 'C__CATG__LOCATION' }, 'get_rack_object_position_' + object.objID)) - } - let objectPositions = await this.axiosRequest(c.url, objectPositionBodies, headers) - if (objectPositions.length >= 1) objectPositions = objectPositions.filter(x => parseInt(x.result[0].pos.title) === parseInt(client.location.slot)) - - // There should only be one segment object for the rack slot if so set it as parent - var chassisId - if (objectPositions.length === 1) chassisId = parseInt(objectPositions[0].result[0].objID) - else { - // Create a new rack segment - const createSegmentParamObject = { - 'apikey': c.apikey, - 'type': 92, - 'title': rackName + ' Slot ' + client.location.slot, - 'categories': { - 'C__CATS__CHASSIS': { - 'data': { - 'front_x': 2, - 'front_y': 1, - 'rear_x': 0, - 'rear_y': 0 - } - }, - 'C__CATS__CHASSIS_SLOT': [ - { - 'title': 'Bay 1', - 'insertion': 'front', - 'from_x': 0, - 'to_x': 0, - 'from_y': 0, - 'to_y': 0 - }, - { - 'title': 'Bay 2', - 'insertion': 'front', - 'from_x': 1, - 'to_x': 1, - 'from_y': 0, - 'to_y': 0 - } - ], - 'C__CATG__LOCATION': { - 'data': { - 'parent': client.parentId, - 'option': client.location.option, - 'insertion': client.location.insertion, - 'pos': client.location.slot - } - } - } - } - - console.log('') - console.log('Create Segment Request:') - const createSegmentParam = this.getBody('cmdb.object.create', createSegmentParamObject, 'create_segment') - const createSegment = await this.axiosRequest(c.url, [createSegmentParam], headers) - chassisId = createSegment[0].result.id - - /* - // Set the new rack units height. (Needs an extra request, why? I DONT KNOW... idoit...) - const setSegmentSizeParams = this.getBody('cmdb.category.save', { 'apikey': c.apikey, - 'object': chassisId, - 'objID': chassisId, - 'category': 'C__CATG__FORMFACTOR', - 'data': { 'rackunits': client.formfactor.rackunits } }, 'set_segment_size') - await this.axiosRequest(c.url, [setSegmentSizeParams], headers) - */ - } - } - } - - // Add categories to the object - if (client.uuid) params.categories.C__CATG__MODEL = { 'data': { 'productid': client.uuid } } - if (client.networks) { - params.categories.C__CATG__IP = [] - for (let index in client.networks) { - const network = client.networks[index] - let networkparams = {} - if (network.ip) networkparams.ipv4_address = network.ip - if (network.hostname) networkparams.hostname = network.hostname - if (network.domain) networkparams.domain = network.domain - if (network.net) networkparams.net = network.net - if (network.primary) networkparams.primary = network.primary ? 1 : 0 - params.categories.C__CATG__IP.push(networkparams) - } - } - - // Add contact assignment to the object. - if (client.contacts) { - // Get the persons ids. - let readPersonBodies = [] - for (let index in client.contacts) { - readPersonBodies.push(this.getBody('cmdb.objects.read', { - 'apikey': c.apikey, - 'filter': { - 'type': 'C__OBJTYPE__PERSON', - 'first_name': client.contacts[index].first_name, - 'last_name': client.contacts[index].last_name - } - }, 'read_persons_' + index)) - } - console.log('') - console.log('Read Person Request:') - const requestReadPersons = await this.axiosRequest(c.url, readPersonBodies, headers) - if (requestReadPersons.error) return requestReadPersons - const error = requestReadPersons.filter(x => x.error) - - if (error.length === 0) { - const personIds = requestReadPersons.map(x => { - if (x.result.length === 1) return x.result[0].id - }).filter(Boolean) - params.categories.C__CATG__CONTACT = [] - for (let index in personIds) { - params.categories.C__CATG__CONTACT.push({ - 'contact': personIds[index] - }) - } - } else console.log(error) - } - - // Add operating system information. - if (client.runtime && client.runtime.operating_system) { - // Get the operating system ids. - console.log('') - console.log('Get OS Request:') - const getOSParam = { - 'apikey': c.apikey, - 'filter': { - 'type': 35, // 35 = Operating System - 'title': client.runtime.operating_system.name - } - } - const getOSBody = this.getBody('cmdb.objects.read', getOSParam, 'get_os') - const requestGetOS = await this.axiosRequest(c.url, [getOSBody], headers) - - // Extra request for getting the id of the version number - console.log('') - console.log('Get OS-Version Request:') - const getOSVersionParam = { - 'apikey': c.apikey, - 'objID': requestGetOS[0].result[0].id, - 'catgID': 'C__CATG__VERSION' - } - const getOSVersionBody = this.getBody('cmdb.category.read', getOSVersionParam, 'get_os_version') - const requestGetOSVersion = await this.axiosRequest(c.url, [getOSVersionBody], headers) - const osVersion = requestGetOSVersion[0].result.filter(x => x.title === client.runtime.operating_system.version) - - // Add the result of the OS request (ids) to the create request. - if (requestGetOS[0].result) { - params.categories.C__CATG__OPERATING_SYSTEM = { - 'data': { - 'application': requestGetOS[0].result[0].id, - 'assigned_version': osVersion[0].id - } - } - } } // Send the create request. - console.log('') - console.log('Create Client Request:') const body = this.getBody('cmdb.object.create', params, 'client_create') const requestCreate = await this.axiosRequest(c.url, [body], headers) @@ -384,82 +195,21 @@ class IdoitBackend extends ExternalBackends { if (requestCreate.error) return { error: requestCreate.errno, message: 'Connection was refused.' } else if (requestCreate[0].error) return { error: 'IDOIT_ERROR', message: requestCreate[0].error.message } - // Add mac address: Network port is a subcategory of network so it need an extra request. - let macRequests = [] - const hostnameIds = requestCreate[0].result.categories.C__CATG__IP - - if (client.networks) { - let macBodies = [] - for (let index in client.networks) { - const network = client.networks[index] - // For the ip adresses - // network.id = requestCreate[0].result.categories.C__CATG__IP[index] - let addresses = [] - // Push the ids as string - if (hostnameIds.length > index) addresses.push('' + hostnameIds[index]) - - let paramsMac = { - 'object': requestCreate[0].result.id, - 'objID': requestCreate[0].result.id, - 'category': 'C__CATG__NETWORK_PORT', - 'data': { - 'mac': network.mac, - 'addresses': addresses - }, - 'apikey': c.apikey - } - - if (network.device) { - if (network.device.speed) { - paramsMac.data.speed = parseFloat(network.device.speed) - // MB/s GB/s ... etc not supported?! Only Mbit/s Gbit/s ... - paramsMac.data.speed_type = null - } - - if (network.device.name) paramsMac.data.title = network.device.name - if (network.device.type) paramsMac.data.port_type = network.device.type - } - - macBodies.push(this.getBody('cmdb.category.save', paramsMac, 'add_mac_address_' + index)) - } - - console.log('') - console.log('Add MAC Request:') - const response = await this.axiosRequest(c.url, macBodies, headers) - macRequests.push(response) - } - - // If chassis id is set, assign the object to the chassis bay - if (chassisId) { - // Read bay ids - const paramsSlots = { - 'object': chassisId, - 'objID': chassisId, - 'category': 'C__CATS__CHASSIS_SLOT', - 'apikey': c.apikey - } - - console.log('') - console.log('Read Rack Slots:') - const readSlotsParam = this.getBody('cmdb.category.read', paramsSlots, 'read_slots') - const readSlots = await this.axiosRequest(c.url, [readSlotsParam], headers) - const bays = readSlots[0].result - - console.log('') - console.log('Assign to Rack Slot:') - const assignToSlotBody = this.getBody('cmdb.category.save', { 'apikey': c.apikey, - 'objID': chassisId, - 'object': chassisId, - 'category': 'C__CATS__CHASSIS_DEVICES', - 'data': { 'assigned_device': requestCreate[0].result.id, 'assigned_slots': [bays[client.location.bay].id] } }, 'assign_to_slot') - await this.axiosRequest(c.url, [assignToSlotBody], headers) - } + // Add id to the client and call the update method for all the other hardware information + client.id = requestCreate[0].result.id + const update = await this.updateClient(credentials, client) // Purpose for Clients: // 1 = Production | 5 = PVS // 2 = Test | 7 = Pool PC // 3 = Quality Assurance | 8 = Mitarbeiter Arbeitsplatz - return { succes: true, id: requestCreate[0].result.id, type: params.type, message: requestCreate[0].result.message, macRequests: macRequests } + return { + succes: true, + id: requestCreate[0].result.id, + type: params.type, + message: requestCreate[0].result.message, + responses: [requestCreate, ...update.response] + } } /* @@ -468,11 +218,43 @@ class IdoitBackend extends ExternalBackends { * credentials: * The client parameters are all optional. * client: { - * id: , title: , parentId: , - * system: { model: , manufacturer: , serialnumber: }, - * cpu: { model: , manufacturer: , type: , frequency: , cores: }, - * ram: [{ title: , manufacturer: , type: , capacity: , unit: }, ...], - * drives: [{model: ,}, serial: , capacity: , unit: , type: , formfactor: , connection: ...] + * id: , + * title: , + * parentId: , + * system: { + * model: , + * manufacturer: , + * serialnumber: + * }, + * cpu: { + * model: , + * manufacturer: , + * type: , + * frequency: , + * cores: + * }, + * ram: [ + * { + * title: , + * manufacturer: , + * type: , + * capacity: , + * unit: + * }, + * ... + * ], + * drives: [ + * { + * model: , + * serial: , + * capacity: , + * unit: , + * type: , + * formfactor: , + * connection: + * }, + * ... + * ] * } */ async updateClient (credentials, client) { @@ -485,190 +267,418 @@ class IdoitBackend extends ExternalBackends { client.parentId = parseInt(client.parentId) let bodies = [] - // workaround for the fucking idoit shit. -.- - let requestResults = [] - - // Update title of the object - if (client.name) bodies.push(this.getBody('cmdb.object.update', { 'id': client.id, 'title': client.name, 'apikey': c.apikey }, 'update_title')) - // 'object' should be 'objID' but there is a fucking bug in the idoit api. Soo let's add both parameters because else it will break when they fix it. - // Update the productid to the uuid. - if (client.uuid) { + // Helper function for creating categorie update requests + let createCategorieRequest = (categorie, method, additionalParams, idCounter = 0) => { + // 'object' should be 'objID' but there is a bug in the idoit api. + // Soo let's add both parameters because else it will break when they fix it. -.-i let params = { 'object': client.id, 'objID': client.id, - 'category': 'C__CATG__MODEL', - 'data': { - 'productid': client.uuid - }, - 'apikey': c.apikey + 'category': categorie, + 'apikey': c.apikey, + ...additionalParams } - bodies.push(this.getBody('cmdb.category.save', params, 'update_uuid')) + return this.getBody(`cmdb.category.${method}`, params, `${method.toUpperCase()}_${categorie}_${idCounter}`) } - if (client.parentId) { - // Update the object. Location - let paramsLocation = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__LOCATION', - 'data': { - 'parent': client.parentId - }, + let createUpdateCategorieRequest = (categorie, data, additionalParams, idCounter) => { + return createCategorieRequest(categorie, 'save', { data: data, ...additionalParams }, idCounter) + } + + // Update title of the object + if (client.name) { + const params = { + 'id': client.id, + 'title': client.name, 'apikey': c.apikey } - bodies.push(this.getBody('cmdb.category.save', paramsLocation, 'update_parent')) + bodies.push(this.getBody('cmdb.object.update', params, 'update_title')) } - // Update the object. Model data. - if (client.system) { - let params = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__MODEL', - 'data': { - 'manufacturer': client.system.manufacturer, - 'title': client.system.model, - 'serial': client.system.serialnumber - }, - 'apikey': c.apikey + // Update the productid to the uuid and model/system data. + if (client.uuid || client.system) { + const modelData = { + ...(client.uuid && { + 'productid': client.uuid + }), + ...(client.system && { + ...(client.system.manufacturer && { + 'manufacturer': client.system.manufacturer + }), + ...(client.system.model && { + 'title': client.system.model + }), + ...(client.system.serialnumber && { + 'serial': client.system.serialnumber + }) + }) } - bodies.push(this.getBody('cmdb.category.save', params, 'update_model')) + + bodies.push(createUpdateCategorieRequest('C__CATG__MODEL', modelData)) } - // Update networks & Check if there is at least one network which will get updated. - if (client.networks && client.networks.filter(network => Object.keys(network).length >= 4).length !== 0) { - let parmReadNetwork = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__IP', - 'apikey': c.apikey + // For all objects, where a client can have mutiple of, we need to check if we need to update a similar entrie or if it's a new one + // Get all data, where the clients wants to update + const getObjectsBodies = [] + + if (client.networks && client.networks.length > 0) { + // Get all ips and macs to check if an update is needed + getObjectsBodies.push(createCategorieRequest('C__CATG__IP', 'read')) + getObjectsBodies.push(createCategorieRequest('C__CATG__NETWORK_PORT', 'read')) + } + if (client.cpus && client.cpus.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__CPU', 'read')) + if (client.gpus && client.gpus.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__GRAPHIC', 'read')) + if (client.ram && client.ram.modules && client.ram.modules.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__MEMORY', 'read')) + if (client.drives && client.drives.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__STORAGE_DEVICE', 'read')) + if (client.monitors && client.monitors.length > 0) getObjectsBodies.push(createCategorieRequest('C__CATG__CUSTOM_FIELDS_MONITOR', 'read')) + if (!client.parentId && client.location && client.location.bay) getObjectsBodies.push(createCategorieRequest('C__CATG__LOCATION', 'read')) + if (client.contacts && client.contacts.length > 0) { + // Get the already attached objects + getObjectsBodies.push(createCategorieRequest('C__CATG__CONTACT', 'read')) + + // Get the whole persons list + getObjectsBodies.push( + this.getBody( + 'cmdb.objects.read', + { + 'apikey': c.apikey, + 'filter': { + 'type': 'C__OBJTYPE__PERSON' + } + }, + 'READ_C__OBJTYPE__PERSON' + ) + ) + } + + const boundObjects = await this.axiosRequest(c.url, getObjectsBodies, headers) + + // Add contact assignment to the object. + if (client.contacts) { + // Get the persons ids. + const persons = boundObjects.filter(response => response.id.startsWith('READ_C__OBJTYPE__PERSON'))[0].result + let counter = 0 + + // Helper method for replaceing ä -> ae, ü -> ue, ö -> oe + const replaceUmlauts = input => { + return input.toLowerCase().replace(/\u00e4/g, 'ae').replace(/\u00fc/g, 'ue').replace(/\u00f6/g, 'oe') } - const ips = await this.axiosRequest(c.url, [this.getBody('cmdb.category.read', parmReadNetwork, 'read_ips')], headers) - parmReadNetwork.category = 'C__CATG__NETWORK_PORT' - const macs = await this.axiosRequest(c.url, [this.getBody('cmdb.category.read', parmReadNetwork, 'read_macs')], headers) - - for (let index in client.networks) { - const network = client.networks[index] - if (network.ip && network.hostname && network.domain) { - // Update ip addresses - let paramsIp = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__IP', - 'data': { + + for (let contact of client.contacts) { + const firstname = replaceUmlauts(contact.firstname) + const lastname = replaceUmlauts(contact.lastname) + + const contactPerson = persons.filter(person => { + if (!replaceUmlauts(person.title).includes(firstname)) return false + else if (!replaceUmlauts(person.title).includes(lastname)) return false + return true + }) + + let contactId + if (contactPerson.length > 0) contactId = contactPerson[0].id + + // Check if the contact is already assigned + const bContacts = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CONTACT')) + let bContactExists = false + if (bContacts.length > 0) { + bContactExists = bContacts[0].result.filter(c => parseInt(c.contact.id) === contactId).length > 0 + } + + if (contactId && !bContactExists) { + const contactData = { + 'contact': contactId, + 'primary': counter === 1 ? 1 : 0 + } + bodies.push(createUpdateCategorieRequest('C__CATG__CONTACT', contactData, {}, counter)) + counter++ + } + } + } + + // Update networks & Check if there is at least one network which will get updated. + if (client.networks && client.networks.length > 0) { + // Returns an array with the existing objects + const ips = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__IP'))[0].result + .map(ip => { + return { + id: ip.id, + ip: ip.ipv4_address.ref_title, + hostname: ip.hostname, + domain: ip.domain + } + }) + + const macs = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__NETWORK_PORT'))[0].result + .map(mac => { + return { + id: mac.id, + name: mac.title, + mac: mac.mac, + ip: mac.addresses.length > 0 ? mac.addresses[0] : undefined + } + }) + + let counter = 0 + for (let network of client.networks) { + let updateIp = true + const networkObject = { + ip: network.ip, + hostname: network.hostname, + domain: network.domain + } + + // Check if there is an existing object with a similar entrie + const similarObjects = [] + for (let ipObj of ips) { + const simplifiedIpObject = { + ip: ipObj.ip, + hostname: ipObj.hostname ? ipObj.hostname : undefined, + domain: ipObj.domain ? ipObj.domain : undefined + } + // Check weather there is already an item which has the same attributes + if (JSON.stringify(networkObject) === JSON.stringify(simplifiedIpObject)) { + updateIp = false + break + } + if (network.ip && network.ip === ipObj.ip) similarObjects.push(ipObj) + else if (network.hostname && network.hostname !== '' && network.hostname === ipObj.hostname) similarObjects.push(ipObj) + // else if (network.ipv6 === ip.ipv6_address.ref_title) similarObjects.push(ipObj) + // else if (network.domain && network.domain === ipObj.domain) similarObjects.push(ipObj) + } + + let ipEntryIds = [] + if (updateIp) { + const ipData = { + ...(network.ip && { 'ipv4_address': network.ip, - 'hostname': network.hostname, - 'domain': network.domain, - 'net': network.net - }, - 'apikey': c.apikey + 'primary': network.primary ? 1 : 0 + }), + ...(network.hostname && { + 'hostname': network.hostname + }), + ...(network.domain && { + 'domain': network.domain + }) + // TODO: ipv6 needs a different host address type. Not possible to set both? + // ...(network.ipv6 && { + // 'ipv6_address': network.ipv6 + // }), + } + + let addParams = {} + + // For now update the first one if there are more + if (similarObjects.length > 0) { + addParams.entry = parseInt(similarObjects[0].id) + } + + if (Object.keys(ipData).length !== 0) { + // If we update an existing entry, we don't need the entry id, else we need to create the object to get the entry id for the mac object. + const ipBody = createUpdateCategorieRequest('C__CATG__IP', ipData, addParams, counter) + if (addParams.entry) { + bodies.push(ipBody) + ipEntryIds.push(addParams.entry) + } else { + const createIpResponse = await this.axiosRequest(c.url, [ipBody], headers) + ipEntryIds.push(createIpResponse[0].result.entry) + } } - if (ips[0].result.length > index) paramsIp.entry = parseInt(ips[0].result[index].id) - bodies.push(this.getBody('cmdb.category.save', paramsIp, 'update_ip')) } - // Update mac addresses - if (network.mac) { - let paramsMac = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__NETWORK_PORT', - 'data': { - 'mac': network.mac - }, - 'apikey': c.apikey + // Check for similar mac objects + let updateMac = true + const similarMacs = [] + for (let mac of macs) { + const simplifiedMacObject = { + name: mac.name, + mac: mac.mac } - if (macs[0].result.length > index) paramsMac.entry = parseInt(macs[0].result[index].id) - bodies.push(this.getBody('cmdb.category.save', paramsMac, 'update_mac')) + + if (JSON.stringify({ name: network.name, mac: network.mac }) === JSON.stringify(simplifiedMacObject)) { + updateMac = false + break + } + if (network.name && network.name === mac.title) similarMacs.push(mac) + // Can't go with mac because two diffrent interface could have the same mac + // else if (network.mac && network.mac === mac.mac) return true + } + + let addParams = {} + if (similarMacs.length > 0) { + addParams.entry = parseInt(similarMacs[0].id) } + + if (updateMac) { + // Update Mac-Address + const macData = { + 'title': network.name, + 'mac': network.mac, + 'addresses': ipEntryIds + } + + bodies.push(createUpdateCategorieRequest('C__CATG__NETWORK_PORT', macData, addParams, counter)) + } + counter++ } } // Update the object. CPU data. - // TODO: Delete cpu if exists? if (client.cpus) { - let counter = 1 + const boundedCpus = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CPU'))[0].result + .map(cpu => { + return { + id: cpu.id, + title: cpu.title, + manufacturer: cpu.manufacturer.title, + type: cpu.type.title, + frequency: cpu.frequency.title, + frequency_unit: parseInt(cpu.frequency_unit.id), + cores: parseInt(cpu.cores) + } + }) + + let counter = 0 for (let cpu of client.cpus) { if (cpu.unit === 'MHz') cpu.unit = 2 else if (cpu.unit === 'GHz') cpu.unit = 3 - let params = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__CPU', - 'data': { - 'manufacturer': cpu.manufacturer, - 'title': cpu.model, - 'type': cpu.type, - 'frequency': parseFloat(cpu.frequency), - 'frequency_unit': cpu.unit, - 'cores': parseInt(cpu.cores) - }, - 'apikey': c.apikey + const cpuData = { + 'title': cpu.model, + 'manufacturer': cpu.manufacturer, + 'type': cpu.type, + 'frequency': parseFloat(cpu.frequency), + 'frequency_unit': cpu.unit, + 'cores': parseInt(cpu.cores) } - counter++ - bodies.push(this.getBody('cmdb.category.save', params, 'create_cpu_' + counter)) + let updateCpu = true + for (let bCpu of boundedCpus) { + let simplifiedCpuObject = { ...bCpu } + delete simplifiedCpuObject.id + + if (JSON.stringify(cpuData) === JSON.stringify(simplifiedCpuObject)) { + updateCpu = false + break + } + } + + if (updateCpu) { + bodies.push(createUpdateCategorieRequest('C__CATG__CPU', cpuData, undefined, counter)) + counter++ + } } } // GPUS if (client.gpus) { - let counter = 1 + const boundedGpus = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__GRAPHIC'))[0].result + .map(gpu => { + return { + id: gpu.id, + title: gpu.title, + manufacturer: gpu.manufacturer.title, + ...(gpu.memory && { 'memory': gpu.memory }), + ...(gpu.unit && { 'unit': gpu.unit }) + } + }) + + let counter = 0 for (let gpu of client.gpus) { - let params = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__GRAPHIC', - 'data': { - 'manufacturer': gpu.manufacturer, - 'title': gpu.model, - 'memory': gpu.memory, - 'unit': gpu.unit - }, - 'apikey': c.apikey + const gpuData = { + 'title': gpu.model, + 'manufacturer': gpu.manufacturer, + ...(gpu.memory && { 'memory': gpu.memory }), + ...(gpu.unit && { 'unit': gpu.unit }) } - counter++ - bodies.push(this.getBody('cmdb.category.save', params, 'create_gpu_' + counter)) + let updateGpu = true + for (let bGpu of boundedGpus) { + let simplifiedGpuObject = { ...bGpu } + delete simplifiedGpuObject.id + + if (JSON.stringify(gpuData) === JSON.stringify(simplifiedGpuObject)) { + updateGpu = false + break + } + } + + if (updateGpu) { + bodies.push(createUpdateCategorieRequest('C__CATG__GRAPHIC', gpuData, undefined, counter)) + counter++ + } } } // Update the object. Ram data. if (client.ram) { - let counter = 1 + const boundedRams = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__MEMORY'))[0].result + .map(bRam => { + return { + id: bRam.id, + title: bRam.title.title, + manufacturer: bRam.manufacturer.title, + capacity: bRam.capacity.title, + unit: bRam.unit.id, + type: bRam.type.title, + description: bRam.description + } + }) + + let counter = 0 for (let module of client.ram.modules) { // Add KB and TB - if (module.unit === 'MB') module.unit = 2 - else if (module.unit === 'GB') module.unit = 3 + if (module.unit === 'MB') module.unit = '2' + else if (module.unit === 'GB') module.unit = '3' + + let ramData = { + 'title': module.model, + 'manufacturer': module.manufacturer, + 'capacity': parseFloat(module.capacity), + 'unit': module.unit, + 'type': module.type, + 'description': JSON.stringify({ + serialnumber: module.serialnumber, + formfactor: module.formfactor, + speed: module.speed + }) + } - let params = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__MEMORY', - 'data': { - 'title': module.model, - 'manufacturer': module.manufacturer, - 'type': module.type, - 'capacity': parseFloat(module.capacity), - 'unit': module.unit, - 'description': JSON.stringify({ - serialnumber: module.serialnumber, - formfactor: module.formfactor, - speed: module.speed - }) - }, - 'apikey': c.apikey + let updateRam = true + for (let bRam of boundedRams) { + let simplifiedRamObject = { ...bRam } + delete simplifiedRamObject.id + + if (JSON.stringify(ramData) === JSON.stringify(simplifiedRamObject)) { + updateRam = false + break + } + } + + if (updateRam) { + bodies.push(createUpdateCategorieRequest('C__CATG__MEMORY', ramData, undefined, counter)) + counter++ } - counter++ - bodies.push(this.getBody('cmdb.category.save', params, 'create_memory_' + counter)) } } // Update the object. Drive data. if (client.drives) { - let counter = 1 + const boundedDrives = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__STORAGE_DEVICE'))[0].result + .map(bDrive => { + return { + id: bDrive.id, + title: bDrive.title, + type: bDrive.type.title, + ...(bDrive.firmware && { firmware: bDrive.firmware }), + ...(bDrive.capacity && { capacity: bDrive.capacity.title }), + ...(bDrive.unit && { unit: parseInt(bDrive.unit.id) }), + ...(bDrive.serial && { serial: bDrive.serial }), + ...(bDrive.connected && { connected: bDrive.connected.title }) + } + }) + + let counter = 0 for (let index in client.drives) { const drive = client.drives[index] // UNIT @@ -683,33 +693,183 @@ class IdoitBackend extends ExternalBackends { else if (drive.type === '7200 rpm') drive.type = 'Hard disk' else if (drive.type === '') drive.type = 'Hard disk' - let params = { - 'object': client.id, - 'objID': client.id, - 'category': 'C__CATG__STORAGE_DEVICE', + let driveData = { + 'title': drive.model, + 'type': drive.type, + ...(drive.firmware && { 'firmware': drive.firmware }), + ...(drive.capacity && { 'capacity': parseFloat(drive.capacity) }), + ...(drive.unit && { 'unit': drive.unit }), + ...(drive.serial && { 'serial': drive.serial }), + ...(drive.connection && { 'connected': drive.connection }) + } + + let updateDrive = true + for (let bDrive of boundedDrives) { + let simplifiedDriveObject = { ...bDrive } + delete simplifiedDriveObject.id + + if (JSON.stringify(driveData) === JSON.stringify(simplifiedDriveObject)) { + updateDrive = false + break + } + } + + if (updateDrive) { + bodies.push(createUpdateCategorieRequest('C__CATG__STORAGE_DEVICE', driveData, undefined, counter)) + counter++ + } + } + } + + // Add Monitors + if (client.monitors) { + // Monitor is a custom object, therefore the fieldnames can be looked up in the idoit interface / api + const boundedMonitors = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__CUSTOM_FIELDS_MONITOR'))[0].result + .map(bMonitor => { + return { + id: bMonitor.id, + f_text_c_1618324877787: bMonitor.f_text_c_1618324877787, + f_text_c_1618324891521: bMonitor.f_text_c_1618324891521, + f_text_c_1618328160730: bMonitor.f_text_c_1618328160730, + f_text_c_1618328167672: bMonitor.f_text_c_1618328167672, + f_text_c_1620145359196: bMonitor.f_text_c_1620145359196, + f_text_c_1622557969975: bMonitor.f_text_c_1622557969975 + } + }) + + let counter = 0 + for (let monitor of client.monitors) { + const monitorData = { + 'f_text_c_1618324877787': monitor.model, // Model + 'f_text_c_1618324891521': monitor.vendor, // Manufacturer + 'f_text_c_1618328160730': `${monitor.resolution.width}x${monitor.resolution.height}`, // Resolution + 'f_text_c_1618328167672': monitor.serialnumber, // Serial Number + 'f_text_c_1620145359196': `${monitor.dimensions.inch}"`, // Display Size (Inch) + 'f_text_c_1622557969975': monitor.port + } + + let updateMonitor = true + for (let bMonitor of boundedMonitors) { + let simplifiedMonitorObject = { ...bMonitor } + delete simplifiedMonitorObject.id + + if (JSON.stringify(monitorData) === JSON.stringify(simplifiedMonitorObject)) { + updateMonitor = false + break + } + } + + if (updateMonitor) { + bodies.push(createUpdateCategorieRequest('C__CATG__CUSTOM_FIELDS_MONITOR', monitorData, undefined, counter)) + counter++ + } + } + } + + /* OPERTATING SYSTEM REWORK TODO + // Add operating system information. + if (client.runtime && client.runtime.operating_system) { + // Get the operating system ids. + const getOSParam = { + 'apikey': c.apikey, + 'filter': { + 'type': 35, // 35 = Operating System + 'title': client.runtime.operating_system.name + } + } + const getOSBody = this.getBody('cmdb.objects.read', getOSParam, 'get_os') + const requestGetOS = await this.axiosRequest(c.url, [getOSBody], headers) + + // Extra request for getting the id of the version number + const getOSVersionParam = { + 'apikey': c.apikey, + 'objID': requestGetOS[0].result[0].id, + 'catgID': 'C__CATG__VERSION' + } + const getOSVersionBody = this.getBody('cmdb.category.read', getOSVersionParam, 'get_os_version') + const requestGetOSVersion = await this.axiosRequest(c.url, [getOSVersionBody], headers) + const osVersion = requestGetOSVersion[0].result.filter(x => x.title === client.runtime.operating_system.version) + + // Add the result of the OS request (ids) to the create request. + if (requestGetOS[0].result) { + params.categories.C__CATG__OPERATING_SYSTEM = { 'data': { - 'category_id': counter, - 'title': drive.model, - 'type': drive.type, - 'firmware': drive.firmware, - // 'manufacturer': , - // 'model': , - 'capacity': parseFloat(drive.capacity), - 'unit': drive.unit, - 'serial': drive.serial, - 'connected': drive.connection - }, - 'apikey': c.apikey + 'application': requestGetOS[0].result[0].id, + 'assigned_version': osVersion[0].id + } } + } + } + */ - bodies.push(this.getBody('cmdb.category.save', params, 'create_drive_' + counter)) - counter++ + // Update the object location. + if (client.parentId && (!client.location || (client.location && !client.location.slot))) { + // Either client or no slot was set + const locationData = { + 'parent': client.parentId + } + + bodies.push(createUpdateCategorieRequest('C__CATG__LOCATION', locationData)) + } else { + // Create segments and prepare bay + + // Rack segmentation + if (!!client.location && !!client.location.slot) { + // Get the parentId if none was given + if (!client.parentId) { + let boundedLocation = boundObjects.filter(response => response.id.startsWith('READ_C__CATG__LOCATION')) + if (boundedLocation.length > 0) boundedLocation = boundedLocation[0].result + if (boundedLocation.length > 0) client.parentId = parseInt(boundedLocation[0].location_path) + } + + // Call method for getting the chassis id, can be either an existing one or a new created one. + const chassisId = await this.getChassisId(c, client, headers) + + // If chassis id is set, assign the object to the chassis bay + if (chassisId) { + // Read bay ids + const paramsSlots = { + 'object': parseInt(chassisId), + 'objID': parseInt(chassisId), + 'category': 'C__CATS__CHASSIS_SLOT', + 'apikey': c.apikey + } + + const readSlotsParam = this.getBody('cmdb.category.read', paramsSlots, 'read_slots') + const readSlots = await this.axiosRequest(c.url, [readSlotsParam], headers) + const bays = readSlots[0].result + + const assignToSlotData = { + 'assigned_device': client.id, + 'assigned_slots': [parseInt(bays[client.location.bay].id)] + } + + bodies.push(createUpdateCategorieRequest('C__CATS__CHASSIS_DEVICES', assignToSlotData, { + 'object': parseInt(chassisId), + 'objID': parseInt(chassisId) + })) + } } } + // Only server have formfactor categories. + if (client.formfactor) { + const formfactorData = { + ...(client.formfactor.formfactor && { + 'formfactor': client.formfactor.formfactor + }), + ...(client.formfactor.rackunits && { + 'rackunits': client.formfactor.rackunits + }) + } + + bodies.push(createUpdateCategorieRequest('C__CATG__FORMFACTOR', formfactorData)) + } + + let requestUpdate = [] + // Send the batch request to idoit if (bodies.length > 0) { - const requestUpdate = await this.axiosRequest(c.url, bodies, headers) - requestResults.push(requestUpdate) + requestUpdate = await this.axiosRequest(c.url, bodies, headers) if (requestUpdate.error) return requestUpdate } @@ -724,12 +884,129 @@ class IdoitBackend extends ExternalBackends { success: true, id: client.id, type: type, - response: requestResults + response: requestUpdate } return result } + /* + * Rack -> Rack obeject + * Slot -> Rack Segment object (contains multiple bays) + * Bay -> Slot objects inside the Rack Segment object + * + */ + async getChassisId (credentials, client, headers) { + // Check if parentId is still missing. If so skip bay assignment. + if (!client.parentId) return undefined + + // Get all assigned objects of the rack (parentid) to check for existing rack segments at slot position. + const rackobjectsBody = this.getBody( + 'cmdb.category.read', + { + 'apikey': credentials.apikey, + 'object': client.parentId, + 'objID': client.parentId, + 'category': 'C__CATG__OBJECT' + }, + 'get_rack_objects' + ) + + // Get the name of the rack + const rackBody = this.getBody( + 'cmdb.category.read', + { + 'apikey': credentials.apikey, + 'object': client.parentId, + 'objID': client.parentId, + 'category': 'C__CATG__GLOBAL' + }, + 'get_rack' + ) + + const rackRequest = await this.axiosRequest(credentials.url, [rackobjectsBody, rackBody], headers) + const rackObjects = rackRequest[0] + const rack = rackRequest[1] + + if (!rackObjects.result || !rack.result) return undefined + + const rackName = rack.result[0].title + let rackSegmentId + + // For each segmentation object in the rack get the slot number + for (let object of rackObjects.result) { + if (object.assigned_object.type !== 'C__OBJTYPE__RACK_SEGMENT') continue + + if (object.assigned_object.title === `${rackName} Slot ${client.location.slot}`) { + // This is the slot where it should get added + rackSegmentId = object.assigned_object.id + break + } + } + + // There should only be one segment object for the rack slot if so set it as parent + let chassisId = rackSegmentId + if (!chassisId) { + // Create a new rack segment + const createSegmentParamObject = { + 'apikey': credentials.apikey, + 'type': 92, + 'title': rackName + ' Slot ' + client.location.slot, + 'categories': { + 'C__CATS__CHASSIS': { + 'data': { + 'front_x': 2, + 'front_y': 1, + 'rear_x': 0, + 'rear_y': 0 + } + }, + 'C__CATS__CHASSIS_SLOT': [ + { + 'title': 'Bay 1', + 'insertion': 'front', + 'from_x': 0, + 'to_x': 0, + 'from_y': 0, + 'to_y': 0 + }, + { + 'title': 'Bay 2', + 'insertion': 'front', + 'from_x': 1, + 'to_x': 1, + 'from_y': 0, + 'to_y': 0 + } + ], + 'C__CATG__LOCATION': { + 'data': { + 'parent': client.parentId, + 'option': client.location.option ? client.location.option : 'Horizontal', + 'insertion': client.location.insertion ? client.location.insertion : 'Front and backside', + 'pos': client.location.slot + } + } + } + } + + const createSegmentParam = this.getBody('cmdb.object.create', createSegmentParamObject, 'create_segment') + const createSegment = await this.axiosRequest(credentials.url, [createSegmentParam], headers) + chassisId = createSegment[0].result.id + + /* + // Set the new rack units height. (Needs an extra request, why? I DONT KNOW... idoit...) + const setSegmentSizeParams = this.getBody('cmdb.category.save', { 'apikey': c.apikey, + 'object': chassisId, + 'objID': chassisId, + 'category': 'C__CATG__FORMFACTOR', + 'data': { 'rackunits': client.formfactor.rackunits } }, 'set_segment_size') + await this.axiosRequest(c.url, [setSegmentSizeParams], headers) + */ + } + return chassisId + } + async getFileList (credentials, externalId) { const c = this.mapCredentials(credentials) const body = this.getBody('cmdb.category.read', { 'objID': externalId, 'category': 'C__CATG__FILE', apikey: c.apikey }, 'filelist') diff --git a/server/package-lock.json b/server/package-lock.json index 4754263..a159fef 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -877,6 +877,16 @@ "safe-buffer": "^5.0.1" } }, + "edid-reader": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/edid-reader/-/edid-reader-0.3.8.tgz", + "integrity": "sha512-lzY7R8go0EXQXW3AtVU4mFB9ITKAtxRZDOC9j6zsu8gXj/iBikbRf+nApPzdduNdTeBocLDIVEoVND+Wa5EDFg==", + "requires": { + "bluebird": "^3.3.4", + "glob": "^7.0.3", + "lodash": "^4.6.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/server/package.json b/server/package.json index 3d794ed..be0dbc3 100644 --- a/server/package.json +++ b/server/package.json @@ -13,6 +13,7 @@ "compression": "^1.7.3", "cookie-parser": "^1.4.4", "debug": "^3.2.6", + "edid-reader": "^0.3.8", "express": "^4.16.4", "express-fileupload": "^1.1.6", "http-errors": "^1.7.2", -- cgit v1.2.3-55-g7522