diff --git a/catalystwan/api/builders/feature_profiles/builder_factory.py b/catalystwan/api/builders/feature_profiles/builder_factory.py index fd39a9f2..0d996a73 100644 --- a/catalystwan/api/builders/feature_profiles/builder_factory.py +++ b/catalystwan/api/builders/feature_profiles/builder_factory.py @@ -9,6 +9,7 @@ from catalystwan.api.builders.feature_profiles.service import ServiceFeatureProfileBuilder from catalystwan.api.builders.feature_profiles.system import SystemFeatureProfileBuilder from catalystwan.api.builders.feature_profiles.transport import TransportAndManagementProfileBuilder +from catalystwan.api.builders.feature_profiles.uc_voice import UcVoiceFeatureProfileBuilder from catalystwan.exceptions import CatalystwanException from catalystwan.models.configuration.feature_profile.common import ProfileType @@ -31,6 +32,7 @@ "transport": TransportAndManagementProfileBuilder, "cli": CliFeatureProfileBuilder, "application-priority": ApplicationPriorityFeatureProfileBuilder, + "uc-voice": UcVoiceFeatureProfileBuilder, } diff --git a/catalystwan/api/builders/feature_profiles/uc_voice.py b/catalystwan/api/builders/feature_profiles/uc_voice.py new file mode 100644 index 00000000..80989bac --- /dev/null +++ b/catalystwan/api/builders/feature_profiles/uc_voice.py @@ -0,0 +1,116 @@ +# Copyright 2024 Cisco Systems, Inc. and its affiliates +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import TYPE_CHECKING, List, Optional +from uuid import UUID + +from catalystwan.api.builders.feature_profiles.report import FeatureProfileBuildReport, handle_build_report +from catalystwan.api.feature_profile_api import UcVoiceFeatureProfileAPI +from catalystwan.endpoints.configuration.feature_profile.sdwan.uc_voice import UcVoiceFeatureProfile +from catalystwan.models.configuration.feature_profile.common import FeatureProfileCreationPayload +from catalystwan.models.configuration.feature_profile.sdwan.uc_voice import ( + AnyUcVoiceParcel, + TranslationProfileParcel, + TranslationRuleParcel, +) + +logger = logging.getLogger(__name__) + +if TYPE_CHECKING: + from catalystwan.session import ManagerSession + + +@dataclass +class TranslationProfile: + tpp: TranslationProfileParcel + calling: Optional[TranslationRuleParcel] = None + called: Optional[TranslationRuleParcel] = None + + +class UcVoiceFeatureProfileBuilder: + """ + A class for building UcVoice feature profiles. + """ + + def __init__(self, session: ManagerSession) -> None: + """ + Initialize a new instance of the Service class. + + Args: + session (ManagerSession): The ManagerSession object used for API communication. + profile_uuid (UUID): The UUID of the profile. + """ + self._profile: FeatureProfileCreationPayload + self._api = UcVoiceFeatureProfileAPI(session) + self._endpoints = UcVoiceFeatureProfile(session) + self._independent_items: List[AnyUcVoiceParcel] = [] + self._translation_profiles: List[TranslationProfile] = [] + + def add_profile_name_and_description(self, feature_profile: FeatureProfileCreationPayload) -> None: + """ + Adds a name and description to the feature profile. + + Args: + name (str): The name of the feature profile. + description (str): The description of the feature profile. + + Returns: + None + """ + self._profile = feature_profile + + def add_parcel(self, parcel: AnyUcVoiceParcel) -> None: + """ + Adds a parcel to the feature profile. + + Args: + parcel (AnySystemParcel): The parcel to add. + + Returns: + None + """ + self._independent_items.append(parcel) + + def add_translation_profile( + self, + tpp: TranslationProfileParcel, + calling: Optional[TranslationRuleParcel] = None, + called: Optional[TranslationRuleParcel] = None, + ): + if not calling and not called: + raise ValueError("There must be at least one translation rule to create a translation profile") + self._translation_profiles.append(TranslationProfile(tpp=tpp, called=called, calling=calling)) + + def build(self) -> FeatureProfileBuildReport: + """ + Builds the feature profile. + + Returns: + UUID: The UUID of the created feature profile. + """ + + profile_uuid = self._endpoints.create_uc_voice_feature_profile(self._profile).id + self.build_report = FeatureProfileBuildReport(profile_uuid=profile_uuid, profile_name=self._profile.name) + for parcel in self._independent_items: + self._create_parcel(profile_uuid, parcel) + for tp in self._translation_profiles: + self._create_translation_profile(profile_uuid, tp) + + return self.build_report + + @handle_build_report + def _create_parcel(self, profile_uuid: UUID, parcel: AnyUcVoiceParcel) -> UUID: + return self._api.create_parcel(profile_uuid, parcel).id + + def _create_translation_profile(self, profile_uuid: UUID, tp: TranslationProfile): + if tp.called: + called_uuid = self._create_parcel(profile_uuid, tp.called) + if called_uuid: + tp.tpp.set_ref_by_call_type(called_uuid, "called") + if tp.calling: + calling_uuid = self._create_parcel(profile_uuid, tp.calling) + if calling_uuid: + tp.tpp.set_ref_by_call_type(calling_uuid, "calling") + self._create_parcel(profile_uuid, tp.tpp) diff --git a/catalystwan/integration_tests/profile_builder/test_pb_uc_voice.py b/catalystwan/integration_tests/profile_builder/test_pb_uc_voice.py new file mode 100644 index 00000000..b8d09223 --- /dev/null +++ b/catalystwan/integration_tests/profile_builder/test_pb_uc_voice.py @@ -0,0 +1,60 @@ +# Copyright 2024 Cisco Systems, Inc. and its affiliates +from catalystwan.api.configuration_groups.parcel import Global +from catalystwan.integration_tests.base import TestCaseBase, create_name_with_run_id +from catalystwan.models.configuration.feature_profile.common import FeatureProfileCreationPayload +from catalystwan.models.configuration.feature_profile.sdwan.uc_voice import ( + TranslationProfileParcel, + TranslationRuleParcel, +) +from catalystwan.models.configuration.feature_profile.sdwan.uc_voice.translation_rule import Action, RuleSettings + + +class TestUcVoiceFeatureProfileBuilder(TestCaseBase): + def setUp(self) -> None: + self.fp_name = create_name_with_run_id("FeatureProfileBuilderUcVoice") + self.fp_description = "Transport feature profile" + self.builder = self.session.api.builders.feature_profiles.create_builder("uc-voice") + self.builder.add_profile_name_and_description( + feature_profile=FeatureProfileCreationPayload(name=self.fp_name, description=self.fp_description) + ) + self.api = self.session.api.sdwan_feature_profiles.transport + + def test_when_build_profile_with_translation_profile_and_rules_expect_success(self): + tp = TranslationProfileParcel(parcel_name="TPP", parcel_description="TTP_Desc", translation_profile_settings=[]) + tr_calling = TranslationRuleParcel( + parcel_name="2", + parcel_description="desc", + rule_name=Global[int](value=2), + rule_settings=[ + RuleSettings( + action=Global[Action](value="replace"), + match=Global[str](value="/123/"), + replacement_pattern=Global[str](value="/444/"), + rule_num=Global[int](value=2), + ) + ], + ) + tr_called = TranslationRuleParcel( + parcel_name="4", + parcel_description="desc", + rule_name=Global[int](value=4), + rule_settings=[ + RuleSettings( + action=Global[Action](value="replace"), + match=Global[str](value="/321/"), + replacement_pattern=Global[str](value="/4445/"), + rule_num=Global[int](value=4), + ) + ], + ) + self.builder.add_translation_profile(tp, tr_calling, tr_called) + # Act + report = self.builder.build() + # Assert + assert len(report.failed_parcels) == 0 + + def tearDown(self) -> None: + target_profile = self.api.get_profiles().filter(profile_name=self.fp_name).single_or_default() + if target_profile: + # In case of a failed test, the profile might not have been created + self.api.delete_profile(target_profile.profile_id) diff --git a/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_profile.py b/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_profile.py index b0b52b1c..9b15051b 100644 --- a/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_profile.py +++ b/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_profile.py @@ -1,5 +1,6 @@ # Copyright 2024 Cisco Systems, Inc. and its affiliates from typing import List, Literal, Optional, Union +from uuid import UUID from pydantic import AliasPath, BaseModel, ConfigDict, Field @@ -27,3 +28,15 @@ class TranslationProfileParcel(_ParcelBase): validation_alias=AliasPath("data", "translationProfileSettings"), description="Translation Profile configuration", ) + + def set_ref_by_call_type(self, ref: UUID, ct: CallType) -> TranslationProfileSettings: + """Set reference UUID to a calling or called rule item or create one and then set the UUID""" + tps = None + for tps_ in self.translation_profile_settings: + if isinstance(tps_.call_type, Global) and tps_.call_type.value == ct: + tps = tps_ + if tps is None: + tps = TranslationProfileSettings(call_type=Global[CallType](value=ct)) + self.translation_profile_settings.append(tps) + tps.translation_rule = RefIdItem.from_uuid(ref) + return tps diff --git a/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_rule.py b/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_rule.py index 24faebe0..cc5f965b 100644 --- a/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_rule.py +++ b/catalystwan/models/configuration/feature_profile/sdwan/uc_voice/translation_rule.py @@ -28,4 +28,4 @@ class TranslationRuleParcel(_ParcelBase): validation_alias=AliasPath("data", "ruleSettings"), description="Translation Rule configuration", ) - rule_name: Optional[Global[int]] = Field(default=None, validation_alias="ruleName", serialization_alias="ruleName") + rule_name: Optional[Global[int]] = Field(default=None, validation_alias=AliasPath("data", "ruleName")) diff --git a/pyproject.toml b/pyproject.toml index 58a58ffe..b10edae7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "catalystwan" -version = "0.40.0dev2" +version = "0.40.0dev3" description = "Cisco Catalyst WAN SDK for Python" authors = ["kagorski "] readme = "README.md"