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

dev-uxmt: push converted localized policy elements to config group profile parcels #747

Merged
merged 4 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions catalystwan/api/feature_profile_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from catalystwan.models.configuration.feature_profile.sdwan.routing import AnyRoutingParcel, RoutingBgpParcel
from catalystwan.models.configuration.feature_profile.sdwan.service import AnyServiceParcel
from catalystwan.models.configuration.feature_profile.sdwan.service.multicast import MulticastParcel
from catalystwan.models.configuration.feature_profile.sdwan.service.route_policy import RoutePolicyParcel
from catalystwan.models.configuration.feature_profile.sdwan.sig_security.sig_security import SIGParcel
from catalystwan.models.configuration.feature_profile.sdwan.topology import AnyTopologyParcel
from catalystwan.models.configuration.feature_profile.sdwan.topology.custom_control import CustomControlParcel
Expand Down Expand Up @@ -306,6 +307,12 @@ def get_parcel(self, profile_id: UUID, parcel_type: Type[Ipv4AclParcel], parcel_
def get_parcel(self, profile_id: UUID, parcel_type: Type[Ipv6AclParcel], parcel_id: UUID) -> Parcel[Ipv6AclParcel]:
...

@overload
def get_parcel(
self, profile_id: UUID, parcel_type: Type[RoutePolicyParcel], parcel_id: UUID
) -> Parcel[RoutePolicyParcel]:
...

def get_parcel(
self, profile_id: UUID, parcel_type: Type[Union[AnyTransportParcel, AnyRoutingParcel]], parcel_id: UUID
) -> Parcel:
Expand Down Expand Up @@ -431,21 +438,21 @@ def delete_all_profiles(self) -> None:
self.delete_profile(profile.profile_id)

def create_parcel(
self, profile_uuid: UUID, payload: AnyServiceParcel, vpn_uuid: Optional[UUID] = None
self, profile_id: UUID, payload: AnyServiceParcel, vpn_uuid: Optional[UUID] = None
) -> ParcelCreationResponse:
"""
Create Service Parcel for selected profile_id based on payload type
"""
if vpn_uuid is not None:
if isinstance(payload, MulticastParcel):
response = self.endpoint.create_service_parcel(profile_uuid, payload._get_parcel_type(), payload)
response = self.endpoint.create_service_parcel(profile_id, payload._get_parcel_type(), payload)
return self.endpoint.associate_parcel_with_vpn(
profile_uuid, vpn_uuid, payload._get_parcel_type(), ParcelAssociationPayload(parcel_id=response.id)
profile_id, vpn_uuid, payload._get_parcel_type(), ParcelAssociationPayload(parcel_id=response.id)
)
else:
parcel_type = payload._get_parcel_type().replace("lan/vpn/", "")
return self.endpoint.create_lan_vpn_sub_parcel(profile_uuid, vpn_uuid, parcel_type, payload)
return self.endpoint.create_service_parcel(profile_uuid, payload._get_parcel_type(), payload)
return self.endpoint.create_lan_vpn_sub_parcel(profile_id, vpn_uuid, parcel_type, payload)
return self.endpoint.create_service_parcel(profile_id, payload._get_parcel_type(), payload)

def delete_parcel(self, profile_uuid: UUID, parcel_type: Type[AnyServiceParcel], parcel_uuid: UUID) -> None:
"""
Expand Down Expand Up @@ -1194,7 +1201,7 @@ def get(
profile_id=profile_id, policy_object_list_type=policy_object_list_type, list_object_id=parcel_id
)

def create(self, profile_id: UUID, payload: AnyPolicyObjectParcel) -> ParcelCreationResponse:
def create_parcel(self, profile_id: UUID, payload: AnyPolicyObjectParcel) -> ParcelCreationResponse:
"""
Create Policy Object for selected profile_id based on payload type
"""
Expand Down
16 changes: 15 additions & 1 deletion catalystwan/api/templates/device_template/device_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import logging
from pathlib import Path
from typing import TYPE_CHECKING, ClassVar, List
from typing import TYPE_CHECKING, ClassVar, List, Optional
from uuid import UUID

from jinja2 import DebugUndefined, Environment, FileSystemLoader, meta # type: ignore
from pydantic import BaseModel, ConfigDict, Field, field_validator
Expand All @@ -15,6 +16,13 @@
logger = logging.getLogger(__name__)


def str_to_uuid(s: str) -> Optional[UUID]:
try:
return UUID(s)
except ValueError:
return None


class GeneralTemplate(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)

Expand Down Expand Up @@ -54,6 +62,12 @@ class DeviceTemplate(BaseModel):
)
policy_id: str = Field(default="", serialization_alias="policyId", validation_alias="policyId")

def get_security_policy_uuid(self) -> Optional[UUID]:
return str_to_uuid(self.security_policy_id)

def get_policy_uuid(self) -> Optional[UUID]:
return str_to_uuid(self.policy_id)

def generate_payload(self) -> str:
env = Environment(
loader=FileSystemLoader(self.payload_path.parent),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_create_extended_community_parcel(self):
ext.add_site_of_origin_community("1.2.3.4", 1000)
ext.add_site_of_origin_community("10.20.30.40", 3000)

self.created_id = self.policy_api.create(self.profile_uuid, ext).id
self.created_id = self.policy_api.create_parcel(self.profile_uuid, ext).id
parcel = self.policy_api.get(self.profile_uuid, ExtendedCommunityParcel, parcel_id=self.created_id)

assert parcel.payload.parcel_name == "ExampleTestName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_create_ssl_decryption_parcel(self):
min_tls_ver="TLSv1.1",
)

self.created_id = self.policy_api.create(self.profile_uuid, ssl_decryption_parcel).id
self.created_id = self.policy_api.create_parcel(self.profile_uuid, ssl_decryption_parcel).id
read_parcel = self.policy_api.get(self.profile_uuid, SslDecryptionParcel, parcel_id=self.created_id)

assert read_parcel.payload.parcel_name == "test_ssl_profile"
Expand Down
38 changes: 37 additions & 1 deletion catalystwan/models/configuration/config_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ class UX1Templates(BaseModel):
validation_alias="deviceTemplates",
)

def create_device_template_by_policy_id_lookup(self) -> Dict[Literal["policy", "security"], Dict[UUID, UUID]]:
lookup: Dict[Literal["policy", "security"], Dict[UUID, UUID]] = {"policy": {}, "security": {}}
for dt in self.device_templates:
policy_id = dt.get_policy_uuid()
security_policy_id = dt.get_security_policy_uuid()
if policy_id is not None:
lookup["policy"][policy_id] = UUID(dt.template_id)
if security_policy_id is not None:
lookup["security"][security_policy_id] = UUID(dt.template_id)
return lookup


class UX1Config(BaseModel):
# All UX1 Configuration items - Mega Model
Expand All @@ -150,13 +161,17 @@ class UX1Config(BaseModel):


class TransformHeader(BaseModel):
model_config = ConfigDict(populate_by_name=True)
type: str = Field(
description="Needed to push item to specific endpoint."
"Type discriminator is not present in many UX2 item payloads"
)
origin: UUID = Field(description="Original UUID of converted item")
origname: Optional[str] = None
subelements: Set[UUID] = Field(default_factory=set)
localized_policy_subelements: Optional[Set[UUID]] = Field(
default=None, serialization_alias="localizedPolicySubelements", validation_alias="localizedPolicySubelements"
)
status: ConvertOutputStatus = Field(default="complete")
info: List[str] = Field(default_factory=list)

Expand Down Expand Up @@ -251,9 +266,30 @@ def insert_parcel_type_from_headers(cls, values: Dict[str, Any]):
profile_parcel["parcel"]["type_"] = profile_parcel["header"]["type"]
return values

def transformed_parcels_with_origin(self, origin: Set[UUID]) -> List[TransformedParcel]:
def list_transformed_parcels_with_origin(self, origin: Set[UUID]) -> List[TransformedParcel]:
return [p for p in self.profile_parcels if p.header.origin in origin]

def add_subelement_in_config_group(
self, profile_types: List[ProfileType], device_template_id: UUID, subelement: UUID
) -> bool:
profile_ids: Set[UUID] = set()
added = False
for config_group in self.config_groups:
if config_group.header.origin == device_template_id:
profile_ids = config_group.header.subelements
break
if not profile_ids:
return added
for feature_profile in self.feature_profiles:
if feature_profile.header.type in profile_types and feature_profile.header.origin in profile_ids:
head = feature_profile.header
if head.localized_policy_subelements is None:
head.localized_policy_subelements = {subelement}
else:
head.localized_policy_subelements.add(subelement)
added = True
return added


class ConfigTransformResult(BaseModel):
# https://docs.pydantic.dev/2.0/usage/models/#fields-with-dynamic-default-values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class MatchEntry(BaseModel):
packet_length: Optional[Global[Union[str, int]]] = Field(
default=None, validation_alias="packetLength", serialization_alias="packetLength"
)
source_data_prefix: Union[SourceDataPrefix, SourceDataPrefixList] = Field(
source_data_prefix: Union[SourceDataPrefix, SourceDataPrefixList, None] = Field(
default=None, validation_alias="sourceDataPrefix", serialization_alias="sourceDataPrefix"
)
source_ports: Optional[List[SourcePorts]] = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,8 @@ class RoutePolicySequence(BaseModel):
)
base_action: Union[Global[AcceptRejectActionType], Default[AcceptRejectActionType]] = Field(
default=as_default("reject", AcceptRejectActionType),
serialization_alias="routePolicyActionType",
validation_alias="routePolicyActionType",
serialization_alias="baseAction",
validation_alias="baseAction",
description="Base Action",
)
protocol: Union[Global[Protocol], Default[Protocol]] = Field(
Expand Down Expand Up @@ -411,7 +411,7 @@ class RoutePolicyParcel(_ParcelBase):
)
default_action: Union[Global[AcceptRejectActionType], Default[AcceptRejectActionType]] = Field(
default=as_default("reject", AcceptRejectActionType),
validation_alias=AliasPath("data", "routePolicyActionType"),
validation_alias=AliasPath("data", "defaultAction"),
description="Default Action",
)
sequences: List[RoutePolicySequence] = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class Sequence(BaseModel):

base_action: Union[Global[AcceptDropActionType], Default[Literal[AcceptDropActionType]]] = Field(
default=Default[Literal[AcceptDropActionType]](value="accept"),
validation_alias="BasicPolicyActionType",
serialization_alias="BasicPolicyActionType",
validation_alias="baseAction",
serialization_alias="baseAction",
)
match_entries: MatchEntries = Field(
validation_alias="matchEntries", serialization_alias="matchEntries", description="Define match conditions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ class Sequence(BaseModel):

base_action: Union[Global[AcceptDropActionType], Default[Literal[AcceptDropActionType]]] = Field(
default=Default[Literal[AcceptDropActionType]](value="accept"),
validation_alias="BasicPolicyActionType",
serialization_alias="BasicPolicyActionType",
validation_alias="baseAction",
serialization_alias="baseAction",
)
match_entries: MatchEntries = Field(
validation_alias="matchEntries", serialization_alias="matchEntries", description="Define match conditions"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from typing_extensions import Annotated

from catalystwan.models.configuration.feature_profile.sdwan.acl import AnyAclParcel
from catalystwan.models.configuration.feature_profile.sdwan.service.route_policy import RoutePolicyParcel

from .cellular_controller import CellularControllerParcel
from .cellular_profile import CellularProfileParcel
Expand Down Expand Up @@ -65,6 +66,7 @@
AnyTransportVpnParcel,
AnyTransportVpnSubParcel,
AnyManagementVpnSubParcel,
RoutePolicyParcel,
],
Field(discriminator="type_"),
]
Expand Down
4 changes: 0 additions & 4 deletions catalystwan/models/policy/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ class PolicyCreationPayload(BaseModel):
default="default description", serialization_alias="policyDescription", validation_alias="policyDescription"
)
policy_type: str = Field(serialization_alias="policyType", validation_alias="policyType")
policy_definition: Union[PolicyDefinition, str] = Field(
serialization_alias="policyDefinition",
validation_alias="policyDefinition",
)
is_policy_activated: bool = Field(
default=False, serialization_alias="isPolicyActivated", validation_alias="isPolicyActivated"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ def test_not_update_parcel_references(self):

updated_parcel = update_parcel_references(aip_parcel, pushed_objects)

assert updated_parcel is aip_parcel
assert updated_parcel.model_dump_json() == aip_parcel.model_dump_json()
12 changes: 11 additions & 1 deletion catalystwan/utils/config_migration/creators/config_pusher.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from catalystwan.models.configuration.feature_profile.sdwan.topology.mesh import MeshParcel
from catalystwan.session import ManagerSession
from catalystwan.utils.config_migration.creators.groups_of_interests_pusher import GroupsOfInterestPusher
from catalystwan.utils.config_migration.creators.localized_policy_pusher import LocalizedPolicyPusher
from catalystwan.utils.config_migration.creators.security_policy_pusher import SecurityPolicyPusher
from catalystwan.utils.config_migration.factories.parcel_pusher import ParcelPusherFactory

Expand Down Expand Up @@ -52,6 +53,13 @@ def __init__(
push_result=self._push_result,
push_context=self._push_context,
)
self._localized_policy_feature_pusher = LocalizedPolicyPusher(
ux2_config=ux2_config,
session=session,
progress=progress,
push_result=self._push_result,
push_context=self._push_context,
)
self._security_policy_pusher = SecurityPolicyPusher(
ux2_config=ux2_config,
session=session,
Expand All @@ -73,6 +81,7 @@ def push(self) -> UX2ConfigPushResult:
self._create_cloud_credentials()
self._create_config_groups()
self._groups_of_interests_pusher.push()
self._localized_policy_feature_pusher.push()
self._security_policy_pusher.push()
self._create_topology_groups(
self._push_context.default_policy_object_profile_id
Expand Down Expand Up @@ -144,6 +153,7 @@ def _create_feature_profile_and_parcels(self, feature_profiles_ids: List[UUID])
profile = pusher.push(transformed_feature_profile.feature_profile, parcels, self._config_map.parcel_map)
feature_profiles.append(profile)
self._push_result.rollback.add_feature_profile(profile.profile_uuid, profile_type)
self._push_context.id_lookup[feature_profile_id] = profile.profile_uuid
except ManagerHTTPError as e:
logger.error(f"Error occured during [{fp_name}] feature profile creation: {e}")
except Exception:
Expand Down Expand Up @@ -205,7 +215,7 @@ def _create_topology_groups(self, default_policy_object_profile_id: Optional[UUI
logger.error(f"Error occured during topology group creation: {e}")
continue

for transformed_parcel in self._ux2_config.transformed_parcels_with_origin(origins):
for transformed_parcel in self._ux2_config.list_transformed_parcels_with_origin(origins):
parcel = transformed_parcel.parcel
if isinstance(parcel, (CustomControlParcel, HubSpokeParcel, MeshParcel)):
try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def push(self) -> None:

try:
parcel = update_parcel_references(parcel, self.push_context.id_lookup)
parcel_id = self._policy_object_api.create(profile_id=profile_id, payload=parcel).id
parcel_id = self._policy_object_api.create_parcel(profile_id=profile_id, payload=parcel).id
profile_rollback.add_parcel(parcel.type_, parcel_id)
self._push_result.report.groups_of_interest.add_created(parcel.parcel_name, parcel_id)
self.push_context.id_lookup[transformed_parcel.header.origin] = parcel_id
Expand Down
Loading