Skip to content
This repository has been archived by the owner on Nov 21, 2024. It is now read-only.

Commit

Permalink
dev-uxmt: stable version of Feature Templates rework (#765)
Browse files Browse the repository at this point in the history
* Feature Templates Models enhancement and unification of common models usage

* enahnce README

* remove unused typeingonre

* Fix: from_merged

* fix usage of summary as param

---------

Co-authored-by: Jakub Krajewski <[email protected]>
  • Loading branch information
cicharka and jpkrajewski authored Jul 16, 2024
1 parent 4673f7c commit e16aabe
Show file tree
Hide file tree
Showing 118 changed files with 24,034 additions and 2,748 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,25 @@ migrate_task.wait_for_completed()
```
</details>

<details>
<summary> <b>Feature Templates</b> <i>(click to expand)</i></summary>

```python
from catalystwan.api.templates.models.omp_vsmart_model import OMPvSmart

omp_vsmart = OMPvSmart(
name="my_first_template",
description="NA",
device_models=["vsmart"]

)

session.api.templates.create(omp_vsmart)
```

More details about how to use and how to add new: [Feature Templates README.md](https://github.com/cisco-open/cisco-catalyst-wan-sdk/blob/main/catalystwan/api/templates/README.md)
</details>

### Note:
To remove `InsecureRequestWarning`, you can include in your scripts (warning is suppressed when `catalystwan_devel` environment variable is set):
```Python
Expand All @@ -413,7 +432,6 @@ except ManagerHTTPError as error:

## [Supported API endpoints](https://github.com/cisco-open/cisco-catalyst-wan-sdk/blob/main/ENDPOINTS.md)


## [Contributing, bug reporting and feature requests](https://github.com/cisco-open/cisco-catalyst-wan-sdk/blob/main/CONTRIBUTING.md)

## Seeking support
Expand Down
83 changes: 46 additions & 37 deletions catalystwan/api/template_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING, Any, Optional, Type, overload

from ciscoconfparse import CiscoConfParse # type: ignore
from typing_extensions import deprecated

from catalystwan.api.task_status_api import Task
from catalystwan.api.templates.cli_template import CLITemplate
Expand All @@ -19,6 +20,7 @@
from catalystwan.api.templates.feature_template import FeatureTemplate
from catalystwan.api.templates.feature_template_field import FeatureTemplateField
from catalystwan.api.templates.feature_template_payload import FeatureTemplatePayload
from catalystwan.api.templates.models.aaa_model import AAAModel
from catalystwan.api.templates.models.cisco_aaa_model import CiscoAAAModel
from catalystwan.api.templates.models.cisco_banner_model import CiscoBannerModel
from catalystwan.api.templates.models.cisco_bfd_model import CiscoBFDModel
Expand All @@ -37,13 +39,15 @@
from catalystwan.api.templates.models.omp_vsmart_model import OMPvSmart
from catalystwan.api.templates.models.security_vsmart_model import SecurityvSmart
from catalystwan.api.templates.models.system_vsmart_model import SystemVsmart
from catalystwan.dataclasses import Device, DeviceTemplateInfo, FeatureTemplateInfo, FeatureTemplatesTypes, TemplateInfo
from catalystwan.api.templates.models.vpn_vsmart_interface_model import VpnVsmartInterfaceModel
from catalystwan.api.templates.models.vpn_vsmart_model import VpnVsmartModel
from catalystwan.dataclasses import Device, FeatureTemplatesTypes
from catalystwan.endpoints.configuration_device_template import FeatureToCLIPayload
from catalystwan.exceptions import AttachedError, TemplateNotFoundError
from catalystwan.models.templates import DeviceTemplateInformation, FeatureTemplateInformation
from catalystwan.exceptions import AttachedError, CatalystwanDeprecationWarning, TemplateNotFoundError
from catalystwan.models.common import DeviceModel
from catalystwan.models.templates import DeviceTemplateInformation, FeatureTemplateInformation, TemplateInformation
from catalystwan.response import ManagerResponse
from catalystwan.typed_list import DataSequence
from catalystwan.utils.device_model import DeviceModel
from catalystwan.utils.dict import merge
from catalystwan.utils.pydantic_field import get_extra_field
from catalystwan.utils.template_type import TemplateType
Expand Down Expand Up @@ -75,15 +79,15 @@ def __init__(self, session: ManagerSession) -> None:
self.session = session

@overload
def get(self, template: Type[DeviceTemplate]) -> DataSequence[DeviceTemplateInfo]: # type: ignore
def get(self, template: Type[DeviceTemplate]) -> DataSequence[DeviceTemplateInformation]:
...

@overload
def get(self, template: Type[FeatureTemplate]) -> DataSequence[FeatureTemplateInfo]: # type: ignore
def get(self, template: Type[FeatureTemplate]) -> DataSequence[FeatureTemplateInformation]:
...

@overload
def get(self, template: Type[CLITemplate]) -> DataSequence[TemplateInfo]: # type: ignore
def get(self, template: Type[CLITemplate]) -> DataSequence[TemplateInformation]:
...

def get(self, template):
Expand All @@ -101,26 +105,17 @@ def get(self, template):
def _get_feature_templates(
self,
summary: bool = True,
offset: Optional[int] = None,
limit: Optional[int] = None,
) -> DataSequence[FeatureTemplateInfo]:
) -> DataSequence[FeatureTemplateInformation]:
"""In a multitenant vManage system, this API is only available in the Provider view."""
endpoint = "/dataservice/template/feature"
params = {"summary": summary}

fr_templates = self.session.get(url=endpoint, params=params)

return fr_templates.dataseq(FeatureTemplateInfo)
return self.session.endpoints.configuration_general_template.get_feature_template_list(
params={"summary": summary}
)

def _get_device_templates(
self, feature: DeviceTemplateFeature = DeviceTemplateFeature.ALL
) -> DataSequence[DeviceTemplateInfo]:
) -> DataSequence[DeviceTemplateInformation]:
"""In a multitenant vManage system, this API is only available in the Provider view."""
endpoint = "/dataservice/template/device"
params = {"feature": feature.value}

templates = self.session.get(url=endpoint, params=params)
return templates.dataseq(DeviceTemplateInfo)
return self.session.endpoints.configuration_template_master.get_device_template_list(params=feature.value)

def attach(self, name: str, device: Device, timeout_seconds: int = 300, **kwargs):
template_type = self.get(DeviceTemplate).filter(name=name).single_or_default().config_type
Expand Down Expand Up @@ -176,17 +171,19 @@ def get_device_specific_variables(name: str):
}

invalid = False
msg = {}
for var in vars:
if var.property not in payload["deviceTemplateList"][0]["device"][0]:
pointer = payload["deviceTemplateList"][0]["device"][0]
if var.property not in kwargs["device_specific_vars"]:
invalid = True
msg[var.property] = "should be provided in attach method as device_specific_vars kwarg."
logger.error(f"{var.property} should be provided in attach method as device_specific_vars kwarg.")
else:
pointer[var.property] = kwargs["device_specific_vars"][var.property] # type: ignore

if invalid:
raise TypeError()
raise TypeError(f"{msg}")

endpoint = "/dataservice/template/device/config/attachfeature"
logger.info(f"Attaching a template: {name} to the device: {device.hostname}.")
Expand Down Expand Up @@ -381,7 +378,7 @@ def edit(self, template):
def _edit_device_template(self, template: DeviceTemplate):
self._create_device_template(template, True)

def _edit_feature_template(self, template: FeatureTemplate, data: FeatureTemplateInfo) -> ManagerResponse:
def _edit_feature_template(self, template: FeatureTemplate, data: FeatureTemplateInformation) -> ManagerResponse:
if self.is_created_by_generator(template):
debug = False
schema = self.get_feature_template_schema(template, debug)
Expand Down Expand Up @@ -438,6 +435,9 @@ def create(self, template, debug: bool = False):
logger.info(f"Template {template.template_name} ({template_type}) was created successfully ({template_id}).")
return template_id

@deprecated(
"Obsolete way to use Feature Templates - only create_by_generator", category=CatalystwanDeprecationWarning
)
def _create_feature_template(self, template: FeatureTemplate) -> str:
payload = template.generate_payload(self.session)
response = self.session.post("/dataservice/template/feature", json=json.loads(payload))
Expand All @@ -454,8 +454,8 @@ def _create_cli_template(self, template: CLITemplate) -> str:

def _create_device_template(self, device_template: DeviceTemplate, edit: bool = False) -> str:
def get_general_template_info(
name: str, fr_templates: DataSequence[FeatureTemplateInfo]
) -> FeatureTemplateInfo:
name: str, fr_templates: DataSequence[FeatureTemplateInformation]
) -> FeatureTemplateInformation:
_template = fr_templates.filter(name=name).single_or_default()

if not _template:
Expand All @@ -465,19 +465,19 @@ def get_general_template_info(

def parse_general_template(
general_template: GeneralTemplate,
fr_templates: DataSequence[FeatureTemplateInfo],
fr_templates: DataSequence[FeatureTemplateInformation],
) -> GeneralTemplate:
if general_template.subTemplates:
general_template.subTemplates = [
parse_general_template(_t, fr_templates) for _t in general_template.subTemplates
if general_template.sub_templates:
general_template.sub_templates = [
parse_general_template(_t, fr_templates) for _t in general_template.sub_templates
]
if general_template.name:
info = get_general_template_info(general_template.name, fr_templates)
return GeneralTemplate(
name=general_template.name,
subTemplates=general_template.subTemplates,
templateId=info.id,
templateType=info.template_type,
sub_templates=general_template.sub_templates,
template_id=info.id,
template_type=info.template_type,
)
else:
return general_template
Expand All @@ -497,9 +497,14 @@ def parse_general_template(
payload = json.loads(device_template.generate_payload())
response = self.session.put(f"/dataservice/template/device/{template_id}", json=payload)
else:
endpoint = "/dataservice/template/device/feature/"
# endpoint = "/dataservice/template/device/feature/"
# response = self.session.post(endpoint, json=payload)
payload = json.loads(device_template.generate_payload())
response = self.session.post(endpoint, json=payload)
response = (
self.session.endpoints.configuration_template_master.create_device_template_from_feature_templates(
payload=payload
)
)

return response.text

Expand All @@ -509,6 +514,7 @@ def is_created_by_generator(self, template: FeatureTemplate) -> bool:
Method will be deleted if every template's payload will be generated dynamically.
"""
ported_templates = (
AAAModel,
CiscoAAAModel,
CiscoBFDModel,
CiscoBannerModel,
Expand All @@ -527,6 +533,8 @@ def is_created_by_generator(self, template: FeatureTemplate) -> bool:
CliTemplateModel,
CiscoSecureInternetGatewayModel,
CiscoOspfv3Model,
VpnVsmartModel,
VpnVsmartInterfaceModel,
)

return isinstance(template, ported_templates)
Expand Down Expand Up @@ -557,7 +565,7 @@ def generate_feature_template_payload(
name=template.template_name,
description=template.template_description,
template_type=template.type,
device_types=[device_model.value for device_model in template.device_models],
device_types=[device_model for device_model in template.device_models],
definition={},
) # type: ignore

Expand All @@ -567,6 +575,7 @@ def generate_feature_template_payload(
for field in fr_template_fields:
value = None
priority_order = None
json_dumped_value = None
# TODO How to discover Device specific variable
if field.key in template.device_specific_variables:
value = template.device_specific_variables[field.key]
Expand Down Expand Up @@ -604,7 +613,7 @@ def validate_device_model(self, template: FeatureTemplate) -> bool:
available_devices_for_template = [device["name"] for device in template_type.device_models]

provided_device_models = [
dev_mod.value if type(dev_mod) is DeviceModel else dev_mod for dev_mod in template.device_models
dev_mod if type(dev_mod) is DeviceModel else dev_mod for dev_mod in template.device_models
]

if not all(dev in available_devices_for_template for dev in provided_device_models):
Expand Down
Loading

0 comments on commit e16aabe

Please sign in to comment.