From 752d9c18bb531544f28bdbfd00b5eb922e07d3d8 Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Mon, 25 Mar 2019 05:43:55 +0000 Subject: [external-backends/idoit] Add server rack segmentation & add multiple ip support --- server/api/registration.js | 11 +- server/ipxe/bash_scripts/addServer.sh | 158 ++++++++++++++++++-- server/ipxe/bash_scripts/grepSystemInfoRework.sh | 2 +- server/ipxe/registration.ipxe | 2 +- .../external-backends/backends/idoit-backend.js | 163 ++++++++++++++++++--- 5 files changed, 296 insertions(+), 40 deletions(-) diff --git a/server/api/registration.js b/server/api/registration.js index 4891e31..20e8618 100644 --- a/server/api/registration.js +++ b/server/api/registration.js @@ -109,7 +109,7 @@ noAuthRouter.post('/group', (req, res) => { /* * Add method for adding a client or server. */ -noAuthRouter.postAsync('/', async (req, res) => { +noAuthRouter.postAsync('/clients', async (req, res) => { let client = req.body.client if (typeof client === 'string') client = JSON.parse(client) @@ -128,13 +128,16 @@ noAuthRouter.postAsync('/', async (req, res) => { // Client does not exist. if (!client.parents) client.parents = [] - const createClient = { name: client.name, description: client.type, ip: client.network.ip, mac: client.network.mac, uuid: client.uuid } + // TODO: Save all IPs? Maybe only primary ip? + // const createClient = { name: client.name, description: client.type, ip: client.network.ip, mac: client.network.mac, uuid: client.uuid } + 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) const newClient = await db.client.create(createClient) client.id = newClient.id // Add groups to the client. - if (client.parents.length === 0) client.parents = await ipHelper.getGroups(client.network.ip) + // if (client.parents.length === 0) client.parents = await ipHelper.getGroups(client.network.ip) + if (client.parents.length === 0) client.parents = await ipHelper.getGroups(client.networks[0].ip) client.parents.forEach(pid => { newClient.addGroup(pid) }) log({ category: 'CLIENT_REGISTRATION', description: 'Client added successfully.', clientId: newClient.id }) @@ -144,7 +147,7 @@ noAuthRouter.postAsync('/', async (req, res) => { else return res.send(result) }) -noAuthRouter.postAsync('/:uuid', async (req, res) => { +noAuthRouter.postAsync('/clients/:uuid', async (req, res) => { let client = req.body.client // Add the name to the ram modules. diff --git a/server/ipxe/bash_scripts/addServer.sh b/server/ipxe/bash_scripts/addServer.sh index ad6e3aa..69088bf 100755 --- a/server/ipxe/bash_scripts/addServer.sh +++ b/server/ipxe/bash_scripts/addServer.sh @@ -1,21 +1,90 @@ #!/bin/bash -json_data() -{ - cat << EOF + + +# We mostly do not care about the return value in function local variable +# assignments. So we can write the declaration and assignment in one line. + + + +die() { + echo -e "$*" 1>&2 + exit 1 +} + + + +usage() { + local file="$(basename "$0")" + + die "$file {-r|--rack}=" \ + "{-s|--slot}=" \ + "[{-b|--bay}=]" \ + "[{-i|--interface=}]" \ + "[--rack-height=]" +} + + + +parse_mgmt() { + local host="$1"; shift + + local dev="$(ipmitool lan print 1)" + cat < 47 || slot < 1 )) && die "Error: Slot not in valid range!" + + + +# json_data +curl -d "$(json_data)" -H "Content-Type: application/json" -X POST --insecure https://bas.intra.uni-freiburg.de/api/registration/clients diff --git a/server/ipxe/bash_scripts/grepSystemInfoRework.sh b/server/ipxe/bash_scripts/grepSystemInfoRework.sh index bdc59b8..ad553fa 100755 --- a/server/ipxe/bash_scripts/grepSystemInfoRework.sh +++ b/server/ipxe/bash_scripts/grepSystemInfoRework.sh @@ -168,4 +168,4 @@ EOF # curl --data "state=6" --insecure https://bas.stfu-kthx.net:8888/api/registrations/$UUID/state # curl -d "name=Client_$UUID&sys_manufacturer=$MANUFACTURER&sys_model=$MODEL&sys_serial=$SERIAL&cpu_model=$CPU_MODEL&cpu_manufacturer=$CPU_MANUFACTURER&cpu_type=$CPU_TYPE&cpu_cores=$CPU_CORES&cpu_frequency=$CPU_FREQUENCY&ram_size=$RAM_SIZE&ram_manufacturer=$RAM_MANUFACTURER&ram_type=$RAM_TYPE&ram_isecc=$RAM_ISECC&ram_formfactor=$RAM_FORMFACTOR&drives=${DRIVES[*]}" -H "Content-Type: application/x-www-form-urlencoded" -X POST --insecure https://bas.intra.uni-freiburg.de/api/registration/$UUID/update #echo $(json_data) -curl -d "$(json_data)" -H "Content-Type: application/json" -X POST --insecure https://bas.intra.uni-freiburg.de/api/registration/$UUID \ No newline at end of file +curl -d "$(json_data)" -H "Content-Type: application/json" -X POST --insecure https://bas.intra.uni-freiburg.de/api/registration/clients/$UUID \ No newline at end of file diff --git a/server/ipxe/registration.ipxe b/server/ipxe/registration.ipxe index 40d6a2a..53f4b01 100644 --- a/server/ipxe/registration.ipxe +++ b/server/ipxe/registration.ipxe @@ -27,7 +27,7 @@ goto start :automatic params param ipxe true -param client { "type": "CLIENT", "uuid": "${uuid}", "purpose": "Pool PC", "network": { "ip": "${net0/ip}", "mac": "${net0/mac}" } } +param client { "type": "CLIENT", "uuid": "${uuid}", "purpose": "Pool PC", "networks": [{ "ip": "${net0/ip}", "mac": "${net0/mac}" }] } chain https://bas.intra.uni-freiburg.de/api/registration##params :key diff --git a/server/lib/external-backends/backends/idoit-backend.js b/server/lib/external-backends/backends/idoit-backend.js index ce7680a..c76b6bf 100644 --- a/server/lib/external-backends/backends/idoit-backend.js +++ b/server/lib/external-backends/backends/idoit-backend.js @@ -181,19 +181,121 @@ class IdoitBackend extends ExternalBackends { } else if (client.type === 'SERVER') { params['type'] = 5 - if (client.location) params.categories.C__CATG__LOCATION = { 'data': { 'parent': client.parentId, 'option': client.location.assembly, 'insertion': client.location.insertion, 'pos': client.location.pos } } + if (client.location && !client.location.bay) 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, 'rackunits': client.formfactor.rackunits } } + + // Rack segmentation + if (client.location.bay) { + // 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 + 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) + const rackName = rack[0].result[0].title + + let objectPositionBodies = [] + + // For each segmentation object in the rack get the slot number + 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': 4, + 'front_y': 2, + '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': 1 + }, + { + 'title': 'Bay 2', + 'insertion': 'front', + 'from_x': 1, + 'to_x': 1, + 'from_y': 0, + 'to_y': 1 + }, + { + 'title': 'Bay 3', + 'insertion': 'front', + 'from_x': 2, + 'to_x': 2, + 'from_y': 0, + 'to_y': 1 + }, + { + 'title': 'Bay 4', + 'insertion': 'front', + 'from_x': 3, + 'to_x': 3, + 'from_y': 0, + 'to_y': 1 + } + ], + 'C__CATG__LOCATION': { + 'data': { + 'parent': parseInt(client.parentId), + 'option': client.location.option, + 'insertion': client.location.insertion, + 'pos': client.location.slot + } + } + } + } + + 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.network) { - // TOOD: - // First read if there are current entries. - // Delete the previous entries. - // Finally create the new entry. - - if (client.network.ip) params.categories.C__CATG__IP = { 'data': { 'category_id': 1, 'ipv4_address': client.network.ip } } + if (client.networks) { + params.categories.C__CATG__IP = [] + for (let network of client.networks) { + 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 + params.categories.C__CATG__IP.push(networkparams) + } } // Send the create request. @@ -205,27 +307,44 @@ class IdoitBackend extends ExternalBackends { 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. - if (client.network && client.network.mac) { - params.categories.C__CATG__NETWORK_PORT = { 'data': { 'category_id': 1, 'mac': client.network.mac } } - const paramsMac = { - 'object': requestCreate[0].result.id, - 'objID': requestCreate[0].result.id, - 'category': 'C__CATG__NETWORK_PORT', - 'data': { - 'category_id': 1, - 'mac': client.network.mac - }, - 'apikey': c.apikey + let macRequests = [] + if (client.networks) { + for (let index in client.networks) { + const network = client.networks[index] + // For the idresses + // network.id = requestCreate[0].result.categories.C__CATG__IP[index] + + const paramsMac = { + 'object': requestCreate[0].result.id, + 'objID': requestCreate[0].result.id, + 'category': 'C__CATG__NETWORK_PORT', + 'data': { + 'mac': network.mac + }, + 'apikey': c.apikey + } + const bodyMac = this.getBody('cmdb.category.save', paramsMac, 'add_mac_address') + const response = await this.axiosRequest(c.url, [bodyMac], headers) + macRequests.push(response) } - const bodyMac = this.getBody('cmdb.category.save', paramsMac, 'add_mac_address') - await this.axiosRequest(c.url, [bodyMac], headers) + } + + // If chassis id is set, assign the object to the chassis bay + if (chassisId) { + const bay = client.location.bay + '' + 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': [bay] } }, 'assign_to_slot') + await this.axiosRequest(c.url, [assignToSlotBody], headers) } // 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 } + return { succes: true, id: requestCreate[0].result.id, type: params.type, message: requestCreate[0].result.message, macRequests: macRequests } } /* -- cgit v1.2.3-55-g7522