From 2514507b87620924cfa08136d083383417acc859 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 28 Mar 2025 13:34:03 -0400 Subject: [PATCH] Add node operations against Nexus switch This enables the commands to work that one would expect. --- .../confluent/networking/nxapi.py | 41 ++++---- .../plugins/hardwaremanagement/nxos.py | 95 +++++++++++++++++++ 2 files changed, 113 insertions(+), 23 deletions(-) create mode 100644 confluent_server/confluent/plugins/hardwaremanagement/nxos.py diff --git a/confluent_server/confluent/networking/nxapi.py b/confluent_server/confluent/networking/nxapi.py index 19e841dd..2db62a4d 100644 --- a/confluent_server/confluent/networking/nxapi.py +++ b/confluent_server/confluent/networking/nxapi.py @@ -20,7 +20,10 @@ def add_sensedata(component, sensedata, name=None): if units == 'Celsius': units = '°C' senseinfo['units'] = units - senseinfo['health'] = _healthmap.get(attrs['operSt'], attrs['operSt']) + senseinfo['health'] = _healthmap.get(attrs['operSt'], 'unknown') + if senseinfo['health'] == 'unknown': + print(senseinfo['health'] + ' not recognized') + senseinfo['health'] = 'critical' elif 'eqptFtSlot' in component: attrs = component['eqptFtSlot']['attributes'] name = '{} {}'.format(attrs['descr'], attrs['physId']) @@ -52,6 +55,8 @@ def add_sensedata(component, sensedata, name=None): senseinfo = {} elif 'eqptPsuSlot' in component: attrs = component['eqptPsuSlot']['attributes'] + senseinfo['value'] = None + senseinfo['units'] = None senseinfo['name'] = 'PSU Slot {}'.format(attrs['physId']) senseinfo['health'] = 'ok' senseinfo['states'] = ['Present'] @@ -66,10 +71,6 @@ def add_sensedata(component, sensedata, name=None): add_sensedata(child, sensedata, name) - - - - class NxApiClient: def __init__(self, switch, user, password, configmanager): self.cachedurls = {} @@ -104,19 +105,6 @@ class NxApiClient: firmdata['BIOS'] = {'version': attrs['biosVersion'], 'date': attrs['biosCompileTime']} return firmdata - - - def get_serial(self): - for imdata in self.grab_imdata('/api/mo/sys/ch.json'): - for keyn in imdata: - currinfo = imdata[keyn] - model = currinfo.get('model', 'Unknown') - serial = currinfo.get('ser', 'Unknown') - modelname = currinfo.get('descr', 'Uknonwn') - - self.wc.grab_json_response_with_status('/api/mo/sys.json') - rsp['imdata'][0]['topSystem']['attributes'][serial] - def get_sensors(self): sensedata = [] for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'): @@ -125,6 +113,18 @@ class NxApiClient: add_sensedata(component, sensedata) return sensedata + def get_health(self): + healthdata = {'health': 'ok', 'sensors': []} + for sensor in self.get_sensors(): + currhealth = sensor.get('health', 'ok') + if currhealth != 'ok': + healthdata['sensors'].append(sensor) + if sensor['health'] == 'critical': + healthdata['health'] = 'critical' + elif sensor['health'] == 'warning' and healthdata['health'] != 'critical': + healthdata['health'] = 'warning' + return healthdata + def get_inventory(self): invdata = [] for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'): @@ -155,11 +155,6 @@ class NxApiClient: invdata.append(invinfo) return invdata - - - - - def grab(self, url, cache=True, retry=True): if cache is True: cache = 1 diff --git a/confluent_server/confluent/plugins/hardwaremanagement/nxos.py b/confluent_server/confluent/plugins/hardwaremanagement/nxos.py new file mode 100644 index 00000000..df5779e8 --- /dev/null +++ b/confluent_server/confluent/plugins/hardwaremanagement/nxos.py @@ -0,0 +1,95 @@ +import confluent.networking.nxapi as nxapi +import eventlet +import eventlet.queue as queue +import eventlet.greenpool as greenpool +import confluent.messages as msg +import traceback + + +def retrieve_node(node, element, user, pwd, configmanager, inputdata, results): + try: + retrieve_node_backend(node, element, user, pwd, configmanager, inputdata, results) + except Exception as e: + print(traceback.format_exc()) + print(repr(e)) + +def simplify_name(name): + return name.lower().replace(' ', '_').replace('/', '-').replace( + '_-_', '-') + +def retrieve_node_backend(node, element, user, pwd, configmanager, inputdata, results): + cli = nxapi.NxApiClient(node, user, pwd, configmanager) + if element == ['power', 'state']: # client initted successfully, must be on + results.put(msg.PowerState(node, 'on')) + elif element == ['health', 'hardware']: + hinfo = cli.get_health() + results.put(msg.HealthSummary(hinfo.get('health', 'unknown'), name=node)) + results.put(msg.SensorReadings(hinfo.get('sensors', []), name=node)) + elif element[:3] == ['inventory', 'hardware', 'all']: + if len(element) == 3: + results.put(msg.ChildCollection('all')) + return + invinfo = cli.get_inventory() + if invinfo: + results.put(msg.KeyValueData({'inventory': invinfo}, node)) + elif element[:3] == ['inventory', 'firmware', 'all']: + if len(element) == 3: + results.put(msg.ChildCollection('all')) + return + fwinfo = [] + for fwnam, fwdat in cli.get_firmware().items(): + fwinfo.append({fwnam: fwdat}) + if fwinfo: + results.put(msg.Firmware(fwinfo, node)) + elif element == ['sensors', 'hardware', 'all']: + sensors = cli.get_sensors() + for sensor in sensors: + results.put(msg.ChildCollection(simplify_name(sensor['name']))) + elif element[:3] == ['sensors', 'hardware', 'all']: + sensors = cli.get_sensors() + for sensor in sensors: + if element[-1] == 'all' or simplify_name(sensor['name']) == element[-1]: + results.put(msg.SensorReadings([sensor], node)) + else: + print(repr(element)) + + +def retrieve(nodes, element, configmanager, inputdata): + results = queue.LightQueue() + workers = set([]) + creds = configmanager.get_node_attributes( + nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True) + for node in nodes: + cred = creds.get(node, {}) + user = cred.get('secret.hardwaremanagementuser', {}).get('value') + pwd = cred.get('secret.hardwaremanagementpassword', {}).get('value') + try: + user = user.decode() + pwd = pwd.decode() + except Exception: + pass + if not user or not pwd: + yield msg.ConfluentTargetInvalidCredentials(node) + continue + workers.add(eventlet.spawn(retrieve_node, node, element, user, pwd, configmanager, inputdata, results)) + while workers: + try: + datum = results.get(block=True, timeout=10) + while datum: + if datum: + yield datum + datum = results.get_nowait() + except queue.Empty: + pass + eventlet.sleep(0.001) + for t in list(workers): + if t.dead: + workers.discard(t) + try: + while True: + datum = results.get_nowait() + if datum: + yield datum + except queue.Empty: + pass +