diff --git a/README.md b/README.md index 3b39faf..eb3d64f 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,27 @@ rest-interface rest-interface session-idle-timeout 120 #optional ``` +### optional_args +optional_args can be set during initialization like this: +```Python +from napalm import get_network_driver + +d = get_network_driver("arubaoss") + +with d('1.2.3.4', 'username', 'password', optional_args={'ssl_verify': False, "debugging": True}) as aruba: + print(aruba.get_config()) +``` + +The following values can be set in optional_args: +- ssl_verify: bool/str = defaults to **True** - will be passed to the requests object (description can be found [here](https://docs.python-requests.org/en/latest/_modules/requests/sessions/#Session.request)) +- keepalive: bool = defaults to **False** - sets the underlying TCP connection to either keep the connection or not and is a workaround for an issue with ArubaOS devices + (discussed [here](https://community.arubanetworks.com/community-home/digestviewer/viewthread?MID=28798#bme4aa3703-e476-4880-9cb4-9b208f86b2f4)) +- keep_alive: bool = same as keepalive, just shadows it to be able to use the same keyword as in older Python requests versions +- debugging: bool = defaults to **False** - sets the level of the logging handler to logging.DEBUG +- disable_ssl_warnings: bool = defaults to **False** - disables ssl warnings from urllib3 +- api: string = defaults to **v6** - defines the API version +- ssl: bool = defaults to **True**, sets http or https + ### Saltstack To use the driver with Saltstack, you would typically need a proxy minion. diff --git a/napalm_arubaoss/helper/base.py b/napalm_arubaoss/helper/base.py index b82681b..f4621e9 100644 --- a/napalm_arubaoss/helper/base.py +++ b/napalm_arubaoss/helper/base.py @@ -1,19 +1,23 @@ """Create the Session.""" -from requests_futures.sessions import FuturesSession -from requests.models import Response -from concurrent.futures import as_completed -from json import JSONDecodeError import base64 import logging -from napalm.base.exceptions import ConnectAuthError +from json import JSONDecodeError + +from requests import Session +from requests.models import Response +from napalm.base.exceptions import ConnectAuthError, NapalmException logger = logging.getLogger("arubaoss.helper.base") +class KeepAliveBoolError(NapalmException): + pass + + class Connection: """Connection class.""" @@ -21,7 +25,7 @@ class Connection: def __init__(self): """Initialize the class.""" - self._apisession = None + self._apisession: Session = Session() self.hostname = "" self.username = "" @@ -62,13 +66,29 @@ def login(self, hostname, username="", password="", timeout=10, optional_args=No url = self.config["api_url"] + "login-sessions" - self._apisession = FuturesSession() + self._apisession = Session() self._apisession.verify = optional_args.get("ssl_verify", True) self._apisession.headers = { "Content-Type": "application/json", - # "Connection": "close" } - self._apisession.keep_alive = optional_args.get("keepalive", True) + + keep_alive = optional_args.get( + "keepalive", + optional_args.get( + "keep_alive", + False + ) + ) + + if not isinstance(keep_alive, bool): + raise KeepAliveBoolError( + "\"keepalive\"/\"keep_alive\" needs to be of type \"bool\"" + ) + + if not keep_alive: + self._apisession.headers.update( + {"Connection": "close"} + ) params = {"userName": self.username, "password": self.password} @@ -103,7 +123,7 @@ def get(self, *args, **kwargs) -> Response: """ ret = self._apisession.get(*args, **kwargs) - return ret.result() + return ret def post(self, *args, **kwargs) -> Response: """ @@ -115,7 +135,7 @@ def post(self, *args, **kwargs) -> Response: """ ret = self._apisession.post(*args, **kwargs) - return ret.result() + return ret def put(self, *args, **kwargs) -> Response: """ @@ -127,7 +147,7 @@ def put(self, *args, **kwargs) -> Response: """ ret = self._apisession.put(*args, **kwargs) - return ret.result() + return ret def delete(self, *args, **kwargs) -> Response: """ @@ -139,7 +159,7 @@ def delete(self, *args, **kwargs) -> Response: """ ret = self._apisession.delete(*args, **kwargs) - return ret.result() + return ret def cli(self, commands): """ @@ -156,28 +176,24 @@ def cli(self, commands): self.cli_output["error"] = "Provide a list of commands" return self.cli_output - async_calls = ( + for command in commands: self._apisession.post( url=url, json={"cmd": command}, timeout=self.timeout, - # bug #4 - random delay while re-using TCP connection - workaround: - # always close the TCP connection - headers={"Content-Type": "application/json", "Connection": "close"}, hooks={ - "response": self._callback(output=self.cli_output, command=command) - }, + "response": self._callback( + output=self.cli_output, + command=command + ) + } ) - for command in commands - ) - - [call.result() for call in as_completed(async_calls)] return self.cli_output def _callback(self, *args, **kwargs): """ - Return Callback for async calls. + Return Callback for request calls. ArubaOSS.cli uses it. diff --git a/napalm_arubaoss/helper/compare_config.py b/napalm_arubaoss/helper/compare_config.py index b2b3dd0..82d37ac 100644 --- a/napalm_arubaoss/helper/compare_config.py +++ b/napalm_arubaoss/helper/compare_config.py @@ -1,6 +1,9 @@ """Backups and commit the configuration, and handles commit confirm.""" import logging + +from time import sleep + from napalm.base.exceptions import CommandErrorException logger = logging.getLogger("arubaoss.helper.compare_config") @@ -26,18 +29,24 @@ def compare_config(self): if not diff.ok: raise CommandErrorException("diff generation failed, raise status") - diff_output = self.connection.get(check_url) - - if not diff_output.status_code == 200: - raise CommandErrorException("diff generation failed, raise status") - - if ( - not diff_output.json()["diff_add_list"] - and not diff_output.json()["diff_remove_list"] - ): - # return empty string to signal the candidate - # and running configs are the same - - return "" - else: - return diff_output.json() + for loop_round in range(1, 6): + # wait a second to give the device time to process + logger.debug(f"loop round \"{loop_round}\"") + sleep(1) + + diff_output = self.connection.get(check_url) + + if not diff_output.status_code == 200: + raise CommandErrorException("diff generation failed, raise status") + + if ( + not diff_output.json()["diff_add_list"] + and not diff_output.json()["diff_remove_list"] + ): + if loop_round == 5: + # return empty string to signal the candidate + # and running configs are the same + return "" + continue + else: + return diff_output.json() diff --git a/requirements.txt b/requirements.txt index 55dd39f..6c8147e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ napalm>=3.3.0 netaddr requests -requests-futures textfsm>=1.1.0 urllib3 \ No newline at end of file