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

Commit

Permalink
Add logic for assigning subparcels
Browse files Browse the repository at this point in the history
  • Loading branch information
jpkrajewski committed Apr 23, 2024
1 parent 38e8373 commit 39b6331
Show file tree
Hide file tree
Showing 19 changed files with 159 additions and 89 deletions.
21 changes: 11 additions & 10 deletions catalystwan/api/builders/feature_profiles/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
LanVpnDhcpServerParcel,
LanVpnParcel,
)
from catalystwan.models.configuration.feature_profile.sdwan.service.multicast import MulticastParcel

if TYPE_CHECKING:
from catalystwan.session import ManagerSession

logger = logging.getLogger(__name__)

IndependedParcels = Annotated[Union[AppqoeParcel, LanVpnDhcpServerParcel], Field(discriminator="type_")]
DependedInterfaceParcels = Annotated[
Union[InterfaceGreParcel, InterfaceSviParcel, InterfaceEthernetParcel, InterfaceIpsecParcel],
DependedVpnSubparcels = Annotated[
Union[InterfaceGreParcel, InterfaceSviParcel, InterfaceEthernetParcel, InterfaceIpsecParcel, MulticastParcel],
Field(discriminator="type_"),
]

Expand All @@ -51,7 +52,7 @@ def __init__(self, session: ManagerSession):
self._endpoints = ServiceFeatureProfile(session)
self._independent_items: List[IndependedParcels] = []
self._independent_items_vpns: Dict[UUID, LanVpnParcel] = {}
self._depended_items_on_vpns: Dict[UUID, List[DependedInterfaceParcels]] = defaultdict(list)
self._depended_items_on_vpns: Dict[UUID, List[DependedVpnSubparcels]] = defaultdict(list)

def add_profile_name_and_description(self, feature_profile: FeatureProfileCreationPayload) -> None:
"""
Expand Down Expand Up @@ -93,9 +94,9 @@ def add_parcel_vpn(self, parcel: LanVpnParcel) -> UUID:
self._independent_items_vpns[vpn_tag] = parcel
return vpn_tag

def add_parcel_vpn_interface(self, vpn_tag: UUID, parcel: DependedInterfaceParcels) -> None:
def add_parcel_vpn_subparcel(self, vpn_tag: UUID, parcel: DependedVpnSubparcels) -> None:
"""
Adds an interface parcel dependent on a VPN to the builder.
Adds an subparcel parcel dependent on a VPN to the builder.
Args:
vpn_tag (UUID): The UUID of the VPN.
Expand All @@ -104,13 +105,13 @@ def add_parcel_vpn_interface(self, vpn_tag: UUID, parcel: DependedInterfaceParce
Returns:
None
"""
logger.debug(f"Adding interface parcel {parcel.parcel_name} to VPN {vpn_tag}")
logger.debug(f"Adding subparcel parcel {parcel.parcel_name} to VPN {vpn_tag}")
self._depended_items_on_vpns[vpn_tag].append(parcel)

def build(self) -> UUID:
"""
Builds the feature profile by creating parcels for independent items,
VPNs, and interface parcels dependent on VPNs.
VPNs, and sub-parcels dependent on VPNs.
Returns:
Service feature profile UUID
Expand All @@ -123,8 +124,8 @@ def build(self) -> UUID:
for vpn_tag, vpn_parcel in self._independent_items_vpns.items():
vpn_uuid = self._api.create_parcel(profile_uuid, vpn_parcel).id

for interface_parcel in self._depended_items_on_vpns[vpn_tag]:
logger.debug(f"Creating interface parcel {interface_parcel.parcel_name} to VPN {vpn_tag}")
self._api.create_parcel(profile_uuid, interface_parcel, vpn_uuid)
for sub_parcel in self._depended_items_on_vpns[vpn_tag]:
logger.debug(f"Creating subparcel parcel {sub_parcel.parcel_name} to VPN {vpn_uuid}")
self._api.create_parcel(profile_uuid, sub_parcel, vpn_uuid)

return profile_uuid
20 changes: 14 additions & 6 deletions catalystwan/api/feature_profile_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional, Protocol, Type, Union, get_args, overload
from typing import TYPE_CHECKING, Any, Optional, Protocol, Type, Union, overload
from uuid import UUID

from pydantic import Json
Expand All @@ -12,7 +12,8 @@
from catalystwan.endpoints.configuration.feature_profile.sdwan.system import SystemFeatureProfile
from catalystwan.models.configuration.feature_profile.sdwan.other import AnyOtherParcel
from catalystwan.models.configuration.feature_profile.sdwan.policy_object.security.url import URLParcel
from catalystwan.models.configuration.feature_profile.sdwan.service import AnyLanVpnInterfaceParcel, AnyServiceParcel
from catalystwan.models.configuration.feature_profile.sdwan.service import AnyServiceParcel
from catalystwan.models.configuration.feature_profile.sdwan.service.multicast import MulticastParcel
from catalystwan.typed_list import DataSequence

if TYPE_CHECKING:
Expand All @@ -27,6 +28,7 @@
FeatureProfileInfo,
GetFeatureProfilesPayload,
Parcel,
ParcelAssociationPayload,
ParcelCreationResponse,
)
from catalystwan.models.configuration.feature_profile.sdwan.policy_object import (
Expand Down Expand Up @@ -238,10 +240,16 @@ def create_parcel(
"""
Create Service Parcel for selected profile_id based on payload type
"""
if type(payload) in get_args(AnyLanVpnInterfaceParcel)[0].__args__:
return self.endpoint.create_lan_vpn_interface_parcel(
profile_uuid, vpn_uuid, payload._get_parcel_type(), payload
)
if vpn_uuid is not None:
if isinstance(payload, MulticastParcel):
response = self.endpoint.create_service_parcel(profile_uuid, 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)
)
else:
return self.endpoint.create_lan_vpn_sub_parcel(
profile_uuid, vpn_uuid, payload._get_parcel_type(), payload
)
return self.endpoint.create_service_parcel(profile_uuid, payload._get_parcel_type(), payload)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
FeatureProfileCreationResponse,
FeatureProfileInfo,
GetFeatureProfilesPayload,
ParcelAssociationPayload,
ParcelCreationResponse,
)
from catalystwan.models.configuration.feature_profile.sdwan.service import (
Expand Down Expand Up @@ -47,8 +48,15 @@ def create_service_parcel(
...

@versions(supported_versions=(">=20.9"), raises=False)
@post("/v1/feature-profile/sdwan/service/{profile_uuid}/lan/vpn/{vpn_uuid}/interface/{parcel_type}")
def create_lan_vpn_interface_parcel(
@post("/v1/feature-profile/sdwan/service/{profile_uuid}/lan/vpn/{vpn_uuid}/{parcel_type}")
def create_lan_vpn_sub_parcel(
self, profile_uuid: UUID, vpn_uuid: UUID, parcel_type: str, payload: AnyLanVpnInterfaceParcel
) -> ParcelCreationResponse:
...

@versions(supported_versions=(">=20.9"), raises=False)
@post("/v1/feature-profile/sdwan/service/{profile_uuid}/lan/vpn/{vpn_uuid}/{parcel_type}")
def associate_parcel_with_vpn(
self, profile_uuid: UUID, vpn_uuid: UUID, parcel_type: str, payload: ParcelAssociationPayload
) -> ParcelCreationResponse:
...
2 changes: 1 addition & 1 deletion catalystwan/models/configuration/feature_profile/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ParcelInfo(BaseModel, Generic[T]):
class ParcelAssociationPayload(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True)

parcel_id: str = Field(alias="parcelId")
parcel_id: UUID = Field(serialization_alias="parcelId", validation_alias="parcelId")


class Prefix(BaseModel):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
Field(discriminator="type_"),
]

AnyAssociatoryParcel = Annotated[
Union[
MulticastParcel,
# DHCP
],
Field(discriminator="type_"),
]

AnyServiceParcel = Annotated[
Union[AnyTopLevelServiceParcel, AnyLanVpnInterfaceParcel],
Field(discriminator="type_"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ class AdvancedEthernetAttributes(BaseModel):


class InterfaceEthernetParcel(_ParcelBase):
type_: Literal["ethernet"] = Field(default="ethernet", exclude=True)
type_: Literal["interface/ethernet"] = Field(default="interface/ethernet", exclude=True)
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, extra="forbid")

shutdown: Union[Global[bool], Variable, Default[bool]] = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class AdvancedGre(BaseModel):


class InterfaceGreParcel(_ParcelBase):
type_: Literal["gre"] = Field(default="gre", exclude=True)
type_: Literal["interface/gre"] = Field(default="interface/gre", exclude=True)
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, extra="forbid")

basic: BasicGre = Field(validation_alias=AliasPath("data", "basic"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class IpsecAddress(BaseModel):


class InterfaceIpsecParcel(_ParcelBase):
type_: Literal["ipsec"] = Field(default="ipsec", exclude=True)
type_: Literal["interface/ipsec"] = Field(default="interface/ipsec", exclude=True)
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, extra="forbid")

interface_name: Union[Global[str], Variable] = Field(validation_alias=AliasPath("data", "ifName"))
Expand All @@ -39,7 +39,7 @@ class InterfaceIpsecParcel(_ParcelBase):
)
tunnel_mode: Optional[Union[Global[IpsecTunnelMode], Default[IpsecTunnelMode]]] = Field(
validation_alias=AliasPath("data", "tunnelMode"),
default=Default[IpsecTunnelMode](value="ipv4"),
default=None,
)
ipsec_description: Union[Global[str], Variable, Default[None]] = Field(
default=Default[None](value=None), validation_alias=AliasPath("data", "description")
Expand All @@ -65,8 +65,8 @@ class InterfaceIpsecParcel(_ParcelBase):
tcp_mss_adjust: Union[Global[int], Variable, Default[None]] = Field(
validation_alias=AliasPath("data", "tcpMssAdjust"), default=Default[None](value=None)
)
tcp_mss_adjust_v6: Union[Global[int], Variable, Default[None]] = Field(
validation_alias=AliasPath("data", "tcpMssAdjustV6"), default=Default[None](value=None)
tcp_mss_adjust_v6: Optional[Union[Global[int], Variable, Default[None]]] = Field(
validation_alias=AliasPath("data", "tcpMssAdjustV6"), default=None
)
clear_dont_fragment: Optional[Union[Global[bool], Variable, Default[bool]]] = Field(
validation_alias=AliasPath("data", "clearDontFragment"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ class AclQos(BaseModel):


class InterfaceSviParcel(_ParcelBase):
type_: Literal["svi"] = Field(default="svi", exclude=True)
type_: Literal["interface/svi"] = Field(default="interface/svi", exclude=True)
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, extra="forbid")

shutdown: Union[Global[bool], Variable, Default[bool]] = Field(
default=Default[bool](value=True), validation_alias=AliasPath("data", "shutdown")
)
interface_name: Union[Global[str], Variable] = Field(validation_alias=AliasPath("data", "interfaceName"))
svi_description: Optional[Union[Global[str], Variable, Default[None]]] = Field(
default=Default[bool](value=True), validation_alias=AliasPath("data", "description")
default=Default[None](value=None), validation_alias=AliasPath("data", "description")
)
interface_mtu: Optional[Union[Global[int], Variable, Default[int]]] = Field(
validation_alias=AliasPath("data", "ifMtu"), default=Default[int](value=1500)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ class StaticNat(BaseModel):
serialization_alias="sourceIp", validation_alias="sourceIp"
)
translated_source_ip: Union[Variable, Global[str], Global[IPv4Address]] = Field(
serialization_alias="translatedSourceIp", validation_alias="translatedSourceIp"
serialization_alias="TranslatedSourceIp", validation_alias="TranslatedSourceIp"
)
static_nat_direction: Union[Variable, Global[Direction]] = Field(
serialization_alias="staticNatDirection", validation_alias="staticNatDirection"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ def create_parcel(self, name: str, description: str, template_values: dict) -> I
Returns:
InterfaceGreParcel: The created InterfaceGreParcel object.
"""
print(template_values)
basic_values, advanced_values = self.prepare_values(template_values)
self.configure_dead_peer_detection(basic_values)
self.configure_ike(basic_values)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from copy import deepcopy
from ipaddress import IPv4Interface, IPv6Address

from catalystwan.api.configuration_groups.parcel import Default, as_global
from catalystwan.models.configuration.feature_profile.sdwan.service.lan.common import IkeGroup
from catalystwan.api.configuration_groups.parcel import Default, as_default, as_global, as_variable
from catalystwan.models.configuration.feature_profile.sdwan.service.lan.common import IkeGroup, TunnelApplication
from catalystwan.models.configuration.feature_profile.sdwan.service.lan.ipsec import InterfaceIpsecParcel, IpsecAddress
from catalystwan.utils.config_migration.converters.exceptions import CatalystwanConverterCantConvertException


class InterfaceIpsecTemplateConverter:
supported_template_types = ("cisco_vpn_interface_ipsec", "vpn-vedge-interface-ipsec")

# Default Values
pre_shared_secret = "{{vpn_if_pre_shared_secret}}"

delete_keys = (
"dead_peer_detection",
"if_name",
Expand All @@ -18,9 +22,11 @@ class InterfaceIpsecTemplateConverter:
"multiplexing",
"ipsec",
"ipv6",
"ip",
)

def create_parcel(self, name: str, description: str, template_values: dict) -> InterfaceIpsecParcel:
print(template_values)
values = deepcopy(template_values)
self.configure_interface_name(values)
self.configure_description(values)
Expand All @@ -32,6 +38,8 @@ def create_parcel(self, name: str, description: str, template_values: dict) -> I
self.configure_ipv6_address(values)
self.configure_address(values)
self.configure_tracker(values)
self.configure_application(values)
self.configure_pre_shared_secret(values)
self.cleanup_keys(values)
return InterfaceIpsecParcel(parcel_name=name, parcel_description=description, **values)

Expand All @@ -42,19 +50,22 @@ def configure_description(self, values: dict) -> None:
values["ipsec_description"] = values.get("description", Default[None](value=None))

def configure_dead_peer_detection(self, values: dict) -> None:
values["dpd_interval"] = values.get("dead_peer_detection", {}).get("dpd_interval")
values["dpd_retries"] = values.get("dead_peer_detection", {}).get("dpd_retries")
values["dpd_interval"] = values.get("dead_peer_detection", {}).get("dpd_interval", as_default(10))
values["dpd_retries"] = values.get("dead_peer_detection", {}).get("dpd_retries", as_default(3))

def configure_ipv6_address(self, values: dict) -> None:
values["ipv6_address"] = values.get("ipv6", {}).get("address")

def configure_address(self, values: dict) -> None:
address = values.get("ip", {}).get("address", {})
if address:
values["address"] = IpsecAddress(
address=as_global(str(address.network.network_address)),
mask=as_global(str(address.network.netmask)),
if not address:
raise CatalystwanConverterCantConvertException(
"Ipsec Address is required in UX2 parcel but in a Feature Template can be optional."
)
values["address"] = IpsecAddress(
address=as_global(str(address.value.network.network_address)),
mask=as_global(str(address.value.network.netmask)),
)

def configure_ike(self, values: dict) -> None:
ike = values.get("ike", {})
Expand Down Expand Up @@ -91,6 +102,13 @@ def configure_tracker(self, values: dict) -> None:
tracker = as_global("".join(tracker.value))
values["tracker"] = tracker

def configure_application(self, values: dict) -> None:
if application := values.get("application"):
values["application"] = as_global(application.value, TunnelApplication)

def configure_pre_shared_secret(self, values: dict) -> None:
values["pre_shared_secret"] = values.get("pre_shared_secret", as_variable(self.pre_shared_secret))

def cleanup_keys(self, values: dict) -> None:
for key in self.delete_keys:
values.pop(key, None)
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ class IgmpToMulticastTemplateConverter:
supported_template_types = ("cedge_igmp", "cisco_IGMP", "igmp")

def create_parcel(self, name: str, description: str, template_values: dict) -> MulticastParcel:
print(template_values)
values = self.prepare_values(template_values)
return MulticastParcel(
parcel_name=name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from .svi import InterfaceSviTemplateConverter
from .thousandeyes import ThousandEyesTemplateConverter
from .ucse import UcseTemplateConverter
from .vpn import LanVpnParcelTemplateConverter
from .vpn import VpnParcelsTemplateConverter

logger = logging.getLogger(__name__)

Expand All @@ -57,7 +57,7 @@
DhcpTemplateConverter,
SNMPTemplateConverter,
AppqoeTemplateConverter,
LanVpnParcelTemplateConverter,
VpnParcelsTemplateConverter,
InterfaceGRETemplateConverter,
InterfaceSviTemplateConverter,
InterfaceEthernetTemplateConverter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class InterfaceSviTemplateConverter:
def create_parcel(self, name: str, description: str, template_values: dict) -> InterfaceSviParcel:
values = deepcopy(template_values)
self.configure_interface_name(values)
self.configure_svi_description(values)
self.configure_ipv4_address(values)
self.configure_ipv6_address(values)
self.configure_arp(values)
Expand Down
Loading

0 comments on commit 39b6331

Please sign in to comment.