diff --git a/iperf3/csclient.py b/iperf3/csclient.py index 2fb8fea..5423c80 100644 --- a/iperf3/csclient.py +++ b/iperf3/csclient.py @@ -1,7 +1,7 @@ """ NCOS communication module for SDK applications. -Copyright (c) 2018 Cradlepoint, Inc. . All rights reserved. +Copyright (c) 2022 Cradlepoint, Inc. . All rights reserved. This file contains confidential information of CradlePoint, Inc. and your use of this file is subject to the CradlePoint Software License Agreement distributed with @@ -62,8 +62,9 @@ def __new__(cls, *na, **kwna): def __init__(self, app_name, init=False): self.app_name = app_name + self.ncos = '/var/mnt/sdk' in os.getcwd() # Running in NCOS handlers = [logging.StreamHandler()] - if sys.platform == 'linux2': + if 'linux' in sys.platform: handlers.append(logging.handlers.SysLogHandler(address='/dev/log')) logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s: %(message)s', datefmt='%b %d %H:%M:%S', handlers=handlers) @@ -91,7 +92,7 @@ def get(self, base, query='', tree=0): A dictionary containing the response (i.e. {"success": True, "data:": {}} """ - if sys.platform == 'linux2': + if 'linux' in sys.platform: cmd = "get\n{}\n{}\n{}\n".format(base, query, tree) return self._dispatch(cmd).get('data') else: @@ -110,6 +111,33 @@ def get(self, base, query='', tree=0): return json.loads(response.text).get('data') + def decrypt(self, base, query='', tree=0): + """ + Constructs and sends a decrypt/get request to retrieve specified data from a device. + + The behavior of this method is contextual: + - If the app is installed on (and executed from) a device, it directly queries the router tree to retrieve the + specified data. + - If the app running remotely from a computer it calls the HTTP GET method to retrieve the specified data. + + Args: + base: String representing a path to a resource on a router tree, + (i.e. '/config/system/logging/level'). + value: Not required. + query: Not required. + tree: Not required. + + Returns: + A dictionary containing the response (i.e. {"success": True, "data:": {}} + + """ + if 'linux' in sys.platform: + cmd = "decrypt\n{}\n{}\n{}\n".format(base, query, tree) + return self._dispatch(cmd).get('data') + else: + # Running in a computer and can't actually send the alert. + print('Decrypt is only available when running the app in NCOS.') + def put(self, base, value='', query='', tree=0): """ Constructs and sends a put request to update or add specified data to the device router tree. @@ -132,7 +160,7 @@ def put(self, base, value='', query='', tree=0): A dictionary containing the response (i.e. {"success": True, "data:": {}} """ value = json.dumps(value) - if sys.platform == 'linux2': + if 'linux' in sys.platform: cmd = "put\n{}\n{}\n{}\n{}\n".format(base, query, tree, value) return self._dispatch(cmd) else: @@ -174,7 +202,7 @@ def post(self, base, value='', query=''): A dictionary containing the response (i.e. {"success": True, "data:": {}} """ value = json.dumps(value) - if sys.platform == 'linux2': + if 'linux' in sys.platform: cmd = f"post\n{base}\n{query}\n{value}\n" return self._dispatch(cmd) else: @@ -195,6 +223,50 @@ def post(self, base, value='', query=''): return json.loads(response.text) + def patch(self, value): + """ + Constructs and sends a patch request to update or add specified data to the device router tree. + + The behavior of this method is contextual: + - If the app is installed on(and executed from) a device, it directly updates or adds the specified data to + the router tree. + - If the app running remotely from a computer it calls the HTTP PUT method to update or add the specified + data. + + Args: + value: list containing dict of add/changes, and list of removals: [{add},[remove]] + + Returns: + A dictionary containing the response (i.e. {"success": True, "data:": {}} + """ + + if 'linux' in sys.platform: + if value[0].get("config"): + adds = value[0] + else: + adds = {"config": value[0]} + adds = json.dumps(adds) + removals = json.dumps(value[1]) + cmd = f"patch\n{adds}\n{removals}\n" + return self._dispatch(cmd) + else: + # Running in a computer so use http to send the put to the device. + import requests + device_ip, username, password = self._get_device_access_info() + device_api = 'http://{}/api/'.format(device_ip) + + try: + response = requests.patch(device_api, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + auth=self._get_auth(device_ip, username, password), + data={"data": '{}'.format(json.dumps(value))}) + except (requests.exceptions.Timeout, + requests.exceptions.ConnectionError): + print("Timeout: device at {} did not respond.".format(device_ip)) + return None + + return json.loads(response.text) + def delete(self, base, query=''): """ Constructs and sends a delete request to delete specified data to the device router tree. @@ -214,7 +286,7 @@ def delete(self, base, query=''): Returns: A dictionary containing the response (i.e. {"success": True, "data:": {}} """ - if sys.platform == 'linux2': + if 'linux' in sys.platform: cmd = "delete\n{}\n{}\n".format(base, query) return self._dispatch(cmd) else: @@ -227,7 +299,7 @@ def delete(self, base, query=''): response = requests.delete(device_api, headers={"Content-Type": "application/x-www-form-urlencoded"}, auth=self._get_auth(device_ip, username, password), - data={"data": '{}'.format(value)}) + data={"data": '{}'.format(base)}) except (requests.exceptions.Timeout, requests.exceptions.ConnectionError): print("Timeout: device at {} did not respond.".format(device_ip)) @@ -249,7 +321,7 @@ def alert(self, value=''): Success: None Failure: An error """ - if sys.platform == 'linux2': + if 'linux' in sys.platform: cmd = "alert\n{}\n{}\n".format(self.app_name, value) return self._dispatch(cmd) else: @@ -267,12 +339,18 @@ def log(self, value=''): Returns: None """ - if sys.platform == 'linux2': + if self.ncos: + # Running in NCOS so write to the logger self.logger.info(value) + elif 'linux' in sys.platform: + # Running in Linux (container?) so write to stdout + with open('/dev/stdout', 'w') as log: + log.write(f'{self.app_name}: {value}\n') else: # Running in a computer so just use print for the log. print(value) + def _get_auth(self, device_ip, username, password): # This is only needed when the app is running in a computer. # Returns the proper HTTP Auth for the global username and password. @@ -306,7 +384,7 @@ def _get_device_access_info(): device_username = '' device_password = '' - if sys.platform != 'linux2': + if 'linux' not in sys.platform: import os import configparser @@ -356,7 +434,7 @@ def _dispatch(self, cmd): # ignore the command error, continue on to next command errmsg = "dispatch failed with exception={} err={}".format(type(err), str(err)) if errmsg is not None: - self.log(self.app_name, errmsg) + self.log(errmsg) pass return result @@ -408,7 +486,7 @@ def _receive(self, sock): # ignore the command error, continue on to next command errmsg = "_receive failed with exception={} err={}".format(type(err), str(err)) if errmsg is not None: - self.log(self.app_name, errmsg) + self.log(errmsg) return result @@ -443,7 +521,7 @@ def start(self): def stop(self): if not self.running: return - self.log(f"Stopping {self.app_name}") + self.log(f"Stopping") for k in list(self.registry.keys()): self.unregister(k) self.event_sock.close()