diff --git a/confluent_client/bin/nodehealth b/confluent_client/bin/nodehealth index 2cba8b92..ebceb16d 100755 --- a/confluent_client/bin/nodehealth +++ b/confluent_client/bin/nodehealth @@ -84,13 +84,13 @@ def main(): healthexplanations[node] = [] for sensor in health[node]['sensors']: explanation = sensor['name'] + ':' - if sensor['value'] is not None: + if sensor.get('value', None) is not None: explanation += str(sensor['value']) - if sensor['units'] is not None: + if sensor.get('units', None) is not None: explanation += sensor['units'] - if sensor['states']: + if sensor.get('states', None): explanation += ',' - if sensor['states']: + if sensor.get('states', None): explanation += ','.join(sensor['states']) healthexplanations[node].append(explanation) if node in healthbynode and node in healthexplanations: diff --git a/confluent_client/bin/nodesensors b/confluent_client/bin/nodesensors index 2222abfd..dc1d749b 100755 --- a/confluent_client/bin/nodesensors +++ b/confluent_client/bin/nodesensors @@ -123,7 +123,7 @@ def sensorpass(showout=True, appendtime=False): if 'sensors' not in reading[node]: continue for sensedata in reading[node]['sensors']: - if sensedata['value'] is None and options.skipnumberless: + if sensedata.get('value', None) is None and options.skipnumberless: continue for redundant_state in ('Non-Critical', 'Critical'): try: @@ -134,17 +134,17 @@ def sensorpass(showout=True, appendtime=False): resultdata[node][sensedata['name']] = sensedata sensorname = sensedata['name'] sensorheaders[sensorname] = sensorname - if sensedata['units'] not in (None, u''): + if sensedata.get('units', None) not in (None, u''): sensorheaders[sensorname] += u' ({0})'.format( sensedata['units']) if showout: - if sensedata['value'] is None: + if sensedata.get('value', None) is None: showval = '' elif isinstance(sensedata['value'], float): showval = u' {0} '.format(floatformat(sensedata['value'])) else: - showval = u' {0} '.format(sensedata['value']) - if sensedata['units'] not in (None, u''): + showval = u' {0} '.format(sensedata.get('value', '')) + if sensedata.get('units', None) not in (None, u''): showval += sensedata['units'] if sensedata.get('health', 'ok') != 'ok': datadescription = [sensedata['health']] diff --git a/confluent_server/confluent/networking/srlinux.py b/confluent_server/confluent/networking/srlinux.py new file mode 100644 index 00000000..38f9d62e --- /dev/null +++ b/confluent_server/confluent/networking/srlinux.py @@ -0,0 +1,230 @@ +import confluent.util as util +import eventlet +webclient = eventlet.import_patched('pyghmi.util.webclient') + + + +class SRLinuxClient: + def __init__(self, switch, user, password, configmanager): + self.cachedurls = {} + self.switch = switch + if configmanager: + cv = util.TLSCertVerifier( + configmanager, switch, 'pubkeys.tls_hardwaremanager' + ).verify_cert + else: + cv = lambda x: True + self.user = user + self.password = password + try: + self.user = self.user.decode() + self.password = self.password.decode() + except Exception: + pass + self.wc = webclient.SecureHTTPConnection(switch, port=443, verifycallback=cv) + self.wc.set_basic_credentials(self.user, self.password) + self.rpc_id = 1 + self.login() + + def login(self): + # Just a quick query to validate that credentials are correct and device is reachable and TLS works out however it is supposed to + self._get_state('/system/information') + + + + + def _rpc_call(self, method, params=None): + """Make a JSON-RPC call to SR-Linux""" + payload = { + 'jsonrpc': '2.0', + 'id': self.rpc_id, + 'method': method, + } + if params: + payload['params'] = params + + self.rpc_id += 1 + + rsp = self.wc.grab_json_response_with_status('/jsonrpc', payload) + if rsp[1] != 200: + raise Exception(f"Failed RPC call: {method}, status: {rsp[1]}") + + result = rsp[0] + if 'error' in result: + raise Exception(f"RPC error: {result['error']}") + + return result.get('result', {}) + + def _get_state(self, path, datastore='state'): + """Get state data from SR-Linux using JSON-RPC get method""" + params = { + 'commands': [ + { + 'path': path, + 'datastore': datastore + } + ] + } + result = self._rpc_call('get', params) + return result + + def get_firmware(self): + """Get firmware/software version information""" + firmdata = {} + result = self._get_state('/system/information') + for item in result: + if 'version' in item: + firmdata['SR-Linux'] = {'version': item['version']} + if 'build-date' in item: + if 'SR-Linux' in firmdata: + firmdata['SR-Linux']['date'] = item['build-date'] + return firmdata + + def get_sensors(self): + """Get sensor readings from the device""" + sensedata = [] + result = self._get_state('/platform/control/temperature') + for item in result: + for pcc in item: + currreading = {} + for reading in item[pcc]: + if reading.get('temperature', {}).get('alarm-status', False): + currreading['health'] = 'critical' + else: + currreading['health'] = 'ok' + states = [] + if reading.get('oper-state', 'up',) != 'up': + states = [reading.get('oper-reason', 'unknown')] + currreading['states'] = states + currreading['name'] = 'Slot {} Temperature'.format(reading.get('slot', 'Unknown')) + currreading['value'] = reading.get('temperature', {}).get('instant', 'Unknown') + currreading['units'] = '°C' + sensedata.append(currreading) + + result = self._get_state('/platform/fan-tray') + for item in result: + for pft in item: + currreading = {} + for reading in item[pft]: + if reading.get('srl_nokia-platform-healthz:healthz', {}).get('status', 'healthy') != 'healthy': + currreading['health'] = 'critical' + else: + currreading['health'] = 'ok' + states = [] + if reading.get('oper-state', 'up',) != 'up': + states = [reading.get('oper-reason', 'unknown')] + currreading['states'] = states + currreading['name'] = 'Fan Tray {}'.format(reading.get('id', 'Unknown')) + currreading['value'] = reading.get('fan', {}).get('speed', 'Unknown') + currreading['units'] = '%' + sensedata.append(currreading) + + result = self._get_state('/platform/power-supply') + for item in result: + for pps in item: + for reading in item[pps]: + currreading = {} + if reading.get('srl_nokia-platform-healthz:healthz', {}).get('status', 'healthy') != 'healthy': + currreading['health'] = 'critical' + else: + currreading['health'] = 'ok' + states = [] + if reading.get('oper-state', 'up',) != 'up': + states = [reading.get('oper-reason', 'unknown')] + currreading['states'] = states + currreading['name'] = 'Power Supply {} Health'.format(reading.get('id', 'Unknown')) + sensedata.append(currreading) + tempreading = {'health': 'ok'} + tempreading['name'] = 'Power Supply {} Temperature'.format(reading.get('id', 'Unknown')) + tempreading['value'] = reading.get('temperature', {}).get('instant', 'Unknown') + tempreading['units'] = '°C' + sensedata.append(tempreading) + for powstat in 'current', 'power', 'voltage': + powreading = {'health': 'ok'} + powreading['name'] = 'Power Supply {} {}'.format(reading.get('id', 'Unknown'), powstat.capitalize()) + powreading['value'] = reading.get('input', {}).get(powstat, 'Unknown') + if powstat == 'current': + powreading['units'] = 'A' + elif powstat == 'power': + powreading['units'] = 'W' + elif powstat == 'voltage': + powreading['units'] = 'V' + sensedata.append(powreading) + return sensedata + + + + def get_health(self): + healthdata = {'health': 'ok', 'sensors': []} + sensors = self.get_sensors() + + for sensor in 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 = [] + results = self._get_state('/platform/chassis') + for result in results: + invinfo = {'name': 'System', 'present': True} + invinfo['information'] = {'Manufacturer': 'Nokia'} + + if isinstance(result, dict): + for key, value in result.items(): + if key == 'serial-number': + invinfo['information']['Serial Number'] = value + elif key == 'part-number': + invinfo['information']['Part Number'] = value + elif key == 'type': + invinfo['information']['Model'] = value + + if invinfo['information']: + invdata.append(invinfo) + return invdata + + def get_mac_table(self): + macdict = {} + response = self._get_state('/network-instance') + for datum in response: + for niname in datum: + for nin in datum[niname]: + if nin.get('type', '').endswith('mac-vrf'): + mactable = nin.get('bridge-table', {}).get('mac-table', {}) + #TODO: process mac table + + return macdict + + def get_lldp(self): + lldpbyport = {} + + response = self._get_state('/system/lldp/interface') + for datum in response: + for intfname in datum: + #TODO: actually process LLDP data + return lldpbyport + + +if __name__ == '__main__': + import sys + import os + from pprint import pprint + myuser = os.environ.get('SWITCHUSER') + mypass = os.environ.get('SWITCHPASS') + if not myuser or not mypass: + print("Set SWITCHUSER and SWITCHPASS environment variables") + sys.exit(1) + + srl = SRLinuxClient(sys.argv[1], myuser, mypass, None) + pprint(srl.get_firmware()) + pprint(srl.get_inventory()) + pprint(srl.get_sensors()) + pprint(srl.get_health()) + pprint(srl.get_lldp()) + pprint(srl.get_mac_table()) diff --git a/confluent_server/confluent/plugins/hardwaremanagement/srlinux.py b/confluent_server/confluent/plugins/hardwaremanagement/srlinux.py new file mode 100644 index 00000000..d20fe6ac --- /dev/null +++ b/confluent_server/confluent/plugins/hardwaremanagement/srlinux.py @@ -0,0 +1,95 @@ +import confluent.networking.srlinux as srlinux +import eventlet +import eventlet.queue as queue +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 = srlinux.SRLinuxClient(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