我有一个使用 PyEZ 库的 Python (3.10) 脚本来连接到 Junos 设备并提交各种显示设置命令。尽管这两个步骤都成功,但在提交候选配置后,无论我为 Device 类和 Config 类的 commit() 方法设置什么超时值,我都会收到 RpcTimeoutError 。我只是不明白为什么会发生这种情况。提交在 5 分钟结束之前完成,因此 commit_config() 方法应返回 True。
我提交的显示设置命令:
delete interfaces ge-0/0/0 unit 500
delete class-of-service interfaces ge-0/0/0 unit 500
delete routing-options rib inet6.0 static route <ipv6 route>,
错误:
Error: RpcTimeoutError(host: hostname, cmd: commit-configuration, timeout: 360)
相关代码如下:
DEVICE_TIMEOUT = 360 # RPC timeout value in seconds
DEVICE_AUTOPROBE = 15
class JunosDeviceConfigurator:
def __init__(self, user=NETCONF_USER, password=NETCONF_PASSWD) -> None:
self.user = user
self.password = password
self.device = None
Device.auto_probe = DEVICE_AUTOPROBE
Device.timeout = DEVICE_TIMEOUT
def connect(self) -> bool:
try:
self.device = Device(
host=self._hostname,
user=self.user,
passwd=self.password,
port=22, huge_tree=True,
gather_facts=True,
timeout=DEVICE_TIMEOUT)
self.device.open()
self.device.timeout = DEVICE_TIMEOUT
self.logger.info(f'Connected to {self._hostname}')
return True
except ConnectRefusedError as err:
self.logger.error(f'Connection refused to {self._hostname}: {str(err)}')
return False
except ConnectError as err:
self.logger.error(f'Connection to {self._hostname} failed: {str(err)}')
return False
except Exception as err:
self.logger.error(f'Error connecting to {self._hostname}: {str(err)}')
return False
def commit_config(self, commands: list, mode = 'exclusive'):
if not self.device:
self.connect()
try:
with Config(self.device, mode=mode) as cu:
for command in commands:
cu.load(command, format='set')
cu.commit(timeout=DEVICE_TIMEOUT)
return True
except Exception as e:
self.logger.error(f'Error: {str(e)}')
return False
我创建了一个解决方法来处理“假”RPC 超时。该方法检查候选配置,如果 cu.diff() 的输出为 None,则该方法返回 True,否则存在 real RPC 超时,重试两次后将返回 False。这是最终的代码:
def commit_config(self, commands: list, mode='exclusive', max_retries=2) -> bool:
"""
Commits configuration changes to a Juniper device using PyEZ.
Args:
commands (list): List of Junos OS configuration commands to be committed.
mode (str, optional): The configuration mode to use ('exclusive' by default).
max_retries (int, optional): Maximum number of retries in case of LockError or RpcTimeoutError.
Returns:
bool: True if the commit was successful, False otherwise.
"""
if not self.device:
self.connect()
for _ in range(max_retries + 1):
try:
with Config(self.device, mode=mode) as cu:
for command in commands:
cu.load(command, format='set')
self.logger.info(f'Trying to commit candidate configuration on {self._hostname}.')
cu.commit(timeout=DEVICE_TIMEOUT)
return True
except RpcTimeoutError as e:
if cu.diff() is not None:
self.logger.warning(f'RpcTimeoutError: {e}. Retrying in {RETRY_DELAY} seconds..')
time.sleep(RETRY_DELAY)
else:
return True # Workaround: return True if the commit was successful despite RpcTimeoutError
except LockError as e:
self.logger.warning(f'LockError: {e}. Retrying in {RETRY_DELAY} seconds..')
time.sleep(RETRY_DELAY)
except ConfigLoadError as e:
self.logger.warning(f'ConfigLoadError: {e}. Retrying in {RETRY_DELAY} seconds..')
time.sleep(RETRY_DELAY)
except CommitError as e:
self.logger.error(e)
except Exception as e:
self.logger.error(f'Error: {str(e)}')
return False
任何改进此代码的建议总是受欢迎的,但这个“修复”将在短期内帮助我..