From 79e3ad53f880a92041da6dab5d13b27c0d58c60b Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 29 Sep 2023 16:23:59 -0400 Subject: [PATCH] Add server side rack layout organization The info is hard to put together client side, but supremely easy server side. Provide a nice call to get the layout for a noderange, similar to (but better than) current GUI code. Now GUI can get a nice canned JSON description of the layout. --- confluent_server/confluent/core.py | 1 + confluent_server/confluent/messages.py | 16 +++ .../confluent/plugins/info/layout.py | 100 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 confluent_server/confluent/plugins/info/layout.py diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index a9ee1dba..f70bc6ae 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -446,6 +446,7 @@ def _init_core(): }, }, }, + 'layout': PluginRoute({'handler': 'layout'}), 'media': { 'uploads': PluginCollection({ 'pluginattrs': ['hardwaremanagement.method'], diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index a24a4d78..ce36344d 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -92,6 +92,7 @@ def msg_deserialize(packed): return cls(*m[1:]) raise Exception("Unknown shenanigans") + class ConfluentMessage(object): apicode = 200 readonly = False @@ -254,6 +255,21 @@ class ConfluentNodeError(object): raise Exception('{0}: {1}'.format(self.node, self.error)) +class Generic(ConfluentMessage): + + def __init__(self, data): + self.data = data + + def json(self): + return json.dumps(self.data) + + def raw(self): + return self.data + + def html(self): + return json.dumps(self.data) + + class ConfluentResourceUnavailable(ConfluentNodeError): apicode = 503 diff --git a/confluent_server/confluent/plugins/info/layout.py b/confluent_server/confluent/plugins/info/layout.py new file mode 100644 index 00000000..8397af7f --- /dev/null +++ b/confluent_server/confluent/plugins/info/layout.py @@ -0,0 +1,100 @@ +# Copyright 2023 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import confluent.core as core +import confluent.messages as msg + +def retrieve(nodes, element, configmanager, inputdata): + locationinfo = configmanager.get_node_attributes(nodes, + (u'enclosure.manager', u'enclosure.bay', u'location.rack', + u'location.row', u'location.u', u'location.height')) + enclosuremap = {} + rackmap = {} + allnodedata = {} + needenclosures = set([]) + locatednodes = set([]) + for node in locationinfo: + nodeinfo = locationinfo[node] + rack = nodeinfo.get(u'location.rack', {}).get('value', '') + u = nodeinfo.get(u'location.u', {}).get('value', None) + row = nodeinfo.get(u'location.row', {}).get('value', '') + enclosure = nodeinfo.get(u'enclosure.manager', {}).get('value', None) + bay = nodeinfo.get(u'enclosure.bay', {}).get('value', None) + height = nodeinfo.get(u'location.height', {}).get('value', None) + if enclosure: + if enclosure not in enclosuremap: + enclosuremap[enclosure] = {} + enclosuremap[enclosure][bay] = node + if u: + if row not in rackmap: + rackmap[row] = {} + if rack not in rackmap[row]: + rackmap[row][rack] = {} + rackmap[row][rack][u] = {'node': enclosure, 'children': enclosuremap[enclosure]} + allnodedata[enclosure] = rackmap[row][rack][u] + if height: + allnodedata[enclosure]['height'] = height + else: # need to see if enclosure lands in the map naturally or need to pull it + needenclosures.add(enclosure) + elif u: + if row not in rackmap: + rackmap[row] = {} + if rack not in rackmap[row]: + rackmap[row][rack] = {} + rackmap[row][rack][u] = {'node': node} + allnodedata[node] = rackmap[row][rack][u] + if height: + allnodedata[node]['height'] = height + locatednodes.add(node) + cfgenc = needenclosures - locatednodes + locationinfo = configmanager.get_node_attributes(cfgenc, (u'location.rack', u'location.row', u'location.u', u'location.height')) + for enclosure in locationinfo: + nodeinfo = locationinfo[enclosure] + rack = nodeinfo.get(u'location.rack', {}).get('value', '') + u = nodeinfo.get(u'location.u', {}).get('value', None) + row = nodeinfo.get(u'location.row', {}).get('value', '') + height = nodeinfo.get(u'location.height', {}).get('value', None) + if u: + allnodedata[enclosure] = {'node': enclosure, 'children': enclosuremap[enclosure]} + if height: + allnodedata[enclosure]['height'] = height + if row not in rackmap: + rackmap[row] = {} + if rack not in rackmap[row]: + rackmap[row][rack] = {} + rackmap[row][rack][u] = allnodedata[enclosure] + results = { + 'errors': [], + 'locations': rackmap, + } + for enclosure in enclosuremap: + if enclosure not in allnodedata: + results['errors'].append('Enclosure {} is missing required location information'.format(enclosure)) + else: + allnodedata[enclosure]['children'] = enclosuremap[enclosure] + needheight = set([]) + for node in allnodedata: + if 'height' not in allnodedata[node]: + needheight.add(node) + needheight = ','.join(needheight) + if needheight: + for rsp in core.handle_path( + '/noderange/{0}/description'.format(needheight), + 'retrieve', configmanager, + inputdata=None): + kvp = rsp.kvpairs + for node in kvp: + allnodedata[node]['height'] = kvp[node]['height'] + yield msg.Generic(results) +