Skip to content

Commit 79c6d9d

Browse files
authored
fix/task-conn-error-handling (#168)
* draft: retry on connection error when polling task status * change exception type * conn drop condition is now parametrized, add condition to template api methods when applicable * bump SDK version
1 parent afc40d6 commit 79c6d9d

4 files changed

Lines changed: 31 additions & 21 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@ dmypy.json
132132
poetry.lock
133133

134134
# Misc
135-
**/.DS_Store
135+
**/.DS_Store
136+
.vscode

catalystwan/api/task_status_api.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import logging
66
from typing import TYPE_CHECKING, List, cast
77

8-
from tenacity import retry, retry_if_result, stop_after_attempt, wait_fixed # type: ignore
8+
from tenacity import retry, retry_if_exception_type, retry_if_result, stop_after_attempt, wait_fixed # type: ignore
9+
from tenacity.retry import retry_base # type: ignore
910

10-
from catalystwan.exceptions import TaskValidationError
11+
from catalystwan.exceptions import ManagerRequestException, TaskValidationError
1112

1213
if TYPE_CHECKING:
1314
from catalystwan.session import ManagerSession
@@ -59,6 +60,7 @@ def wait_for_completed(
5960
failure_statuses_ids: List[OperationStatusId] = [
6061
OperationStatusId.FAILURE,
6162
],
63+
expect_conn_drop: bool = False,
6264
) -> TaskResult:
6365
"""
6466
Method to check subtasks statuses of the task
@@ -91,6 +93,8 @@ def wait_for_completed(
9193
success_statuses_ids (Union[List[OperationStatus], str]): list of positive sub-tasks statuses id's
9294
fails_statuses_id (Union[List[OperationStatusId], str]): list of negative sub-tasks statuses
9395
fails_statuses_ids (Union[List[OperationStatusId], str]): list of negative sub-tasks statuses id's
96+
expect_conn_drop (bool): set to true when performing action which can result in connection drop like:
97+
server reboot or server configuration change
9498
9599
Returns:
96100
TaskResult(): result attr is True if all subtasks are success
@@ -136,10 +140,14 @@ def check_status(task_data: List[SubTaskData]) -> bool:
136140
def log_exception(self) -> None:
137141
logger.error("Operation status not achieved in given time")
138142

143+
retry_condition: retry_base = retry_if_result(check_status)
144+
if expect_conn_drop:
145+
retry_condition |= retry_if_exception_type(ManagerRequestException)
146+
139147
@retry(
140148
wait=wait_fixed(interval_seconds),
141149
stop=stop_after_attempt(int(timeout_seconds / interval_seconds)),
142-
retry=retry_if_result(check_status),
150+
retry=retry_condition,
143151
retry_error_callback=log_exception,
144152
)
145153
def wait_for_action_finish() -> List[SubTaskData]:

catalystwan/api/template_api.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from catalystwan.typed_list import DataSequence
4545
from catalystwan.utils.device_model import DeviceModel
4646
from catalystwan.utils.dict import merge
47+
from catalystwan.utils.personality import Personality
4748
from catalystwan.utils.pydantic_field import get_extra_field
4849
from catalystwan.utils.template_type import TemplateType
4950

@@ -190,7 +191,9 @@ def get_device_specific_variables(name: str):
190191
endpoint = "/dataservice/template/device/config/attachfeature"
191192
logger.info(f"Attaching a template: {name} to the device: {device.hostname}.")
192193
response = self.session.post(url=endpoint, json=payload).json()
193-
task = Task(session=self.session, task_id=response["id"]).wait_for_completed(timeout_seconds=timeout_seconds)
194+
task = Task(session=self.session, task_id=response["id"]).wait_for_completed(
195+
timeout_seconds=timeout_seconds, expect_conn_drop=device.personality is Personality.VMANAGE
196+
)
194197
if task.result:
195198
return True
196199
logger.warning(f"Failed to attach template: {name} to the device: {device.hostname}.")
@@ -235,7 +238,9 @@ def _attach_cli(self, name: str, device: Device, is_edited: bool = False, timeou
235238
endpoint = "/dataservice/template/device/config/attachcli"
236239
logger.info(f"Attaching a template: {name} to the device: {device.hostname}.")
237240
response = self.session.post(url=endpoint, json=payload).json()
238-
task = Task(session=self.session, task_id=response["id"]).wait_for_completed(timeout_seconds=timeout_seconds)
241+
task = Task(session=self.session, task_id=response["id"]).wait_for_completed(
242+
timeout_seconds=timeout_seconds, expect_conn_drop=device.personality is Personality.VMANAGE
243+
)
239244
if task.result:
240245
return True
241246
logger.warning(f"Failed to attach tempate: {name} to the device: {device.hostname}.")
@@ -259,7 +264,9 @@ def deatach(self, device: Device) -> bool:
259264
endpoint = "/dataservice/template/config/device/mode/cli"
260265
logger.info(f"Changing mode to cli mode for {device.hostname}.")
261266
response = self.session.post(url=endpoint, json=payload).json()
262-
task = Task(session=self.session, task_id=response["id"]).wait_for_completed()
267+
task = Task(session=self.session, task_id=response["id"]).wait_for_completed(
268+
expect_conn_drop=device.personality is Personality.VMANAGE
269+
)
263270
if task.result:
264271
return True
265272
logger.warning(f"Failed to change to cli mode for device: {device.hostname}.")
@@ -353,16 +360,13 @@ def _delete_cli_template(self, name: str) -> bool:
353360
return True
354361

355362
@overload
356-
def edit(self, template: FeatureTemplate) -> Any:
357-
...
363+
def edit(self, template: FeatureTemplate) -> Any: ...
358364

359365
@overload
360-
def edit(self, template: CLITemplate) -> Any:
361-
...
366+
def edit(self, template: CLITemplate) -> Any: ...
362367

363368
@overload
364-
def edit(self, template: DeviceTemplate) -> Any:
365-
...
369+
def edit(self, template: DeviceTemplate) -> Any: ...
366370

367371
def edit(self, template):
368372
template_info = self.get(template).filter(name=template.template_name).single_or_default()
@@ -394,16 +398,13 @@ def _edit_feature_template(self, template: FeatureTemplate, data: FeatureTemplat
394398
return response
395399

396400
@overload
397-
def create(self, template: FeatureTemplate, debug=False) -> str:
398-
...
401+
def create(self, template: FeatureTemplate, debug=False) -> str: ...
399402

400403
@overload
401-
def create(self, template: DeviceTemplate) -> str:
402-
...
404+
def create(self, template: DeviceTemplate) -> str: ...
403405

404406
@overload
405-
def create(self, template: CLITemplate) -> str:
406-
...
407+
def create(self, template: CLITemplate) -> str: ...
407408

408409
def create(self, template, debug: bool = False):
409410
if isinstance(template, list):

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[tool.poetry]
22
name = "catalystwan"
3-
version = "0.41.3"
3+
version = "0.41.4"
44
description = "Cisco Catalyst WAN SDK for Python"
5-
authors = ["kagorski <kagorski@cisco.com>"]
5+
authors = ["sbasan <sbasan@cisco.com>"]
66
readme = "README.md"
77
repository = "https://github.com/cisco-en-programmability/catalystwan-sdk"
88

0 commit comments

Comments
 (0)