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

Commit

Permalink
Add OSPFv3IP4 converter.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpkrajewski committed Apr 2, 2024
1 parent a81a5f4 commit 919e63d
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@
from pydantic import Field
from typing_extensions import Annotated

from catalystwan.models.configuration.feature_profile.sdwan.service.ospf import OspfParcel

from .appqoe import AppqoeParcel
from .dhcp_server import LanVpnDhcpServerParcel
from .lan.ethernet import InterfaceEthernetParcel
from .lan.gre import InterfaceGreParcel
from .lan.ipsec import InterfaceIpsecParcel
from .lan.svi import InterfaceSviParcel
from .lan.vpn import LanVpnParcel
from .ospf import OspfParcel
from .ospfv3 import Ospfv3IPv4Parcel, Ospfv3IPv6Parcel

AnyTopLevelServiceParcel = Annotated[
Union[
LanVpnDhcpServerParcel,
AppqoeParcel,
LanVpnParcel,
OspfParcel,
Ospfv3IPv4Parcel,
Ospfv3IPv6Parcel,
# TrackerGroupData,
# WirelessLanData,
# SwitchportData
Expand Down Expand Up @@ -46,6 +48,8 @@
"AppqoeParcel",
"LanVpnParcel",
"OspfParcel",
"Ospfv3IPv4Parcel",
"Ospfv3IPv6Parcel",
"InterfaceSviParcel",
"InterfaceGreParcel",
"AnyServiceParcel",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Copyright 2024 Cisco Systems, Inc. and its affiliates

from ipaddress import IPv4Address
from typing import List, Literal, Optional, Union
from uuid import UUID

from pydantic import AliasPath, BaseModel, ConfigDict, Field

from catalystwan.api.configuration_groups.parcel import Default, Global, Variable, _ParcelBase
from catalystwan.models.common import MetricType
from catalystwan.models.configuration.feature_profile.common import Prefix

NetworkType = Literal[
Expand Down Expand Up @@ -40,7 +42,6 @@
"omp",
"eigrp",
]
MetricType = Literal["type1", "type2"]


class NoAuth(BaseModel):
Expand Down Expand Up @@ -171,7 +172,7 @@ class Ospfv3IPv6Area(BaseModel):
serialization_alias="areaTypeConfig", validation_alias="areaTypeConfig", default=None
)
interfaces: List[Ospfv3InterfaceParametres]
ranges: Optional[List[SummaryRoute]] = None
ranges: Optional[List[SummaryRouteIPv6]] = None


class MaxMetricRouterLsa(BaseModel):
Expand Down Expand Up @@ -210,7 +211,9 @@ class DefaultOriginate(BaseModel):
originate: Union[Global[bool], Default[bool]]
always: Optional[Union[Global[bool], Variable, Default[bool]]] = None
metric: Optional[Union[Global[str], Variable, Default[None]]] = None
metricType: Optional[Union[Global[MetricType], Variable, Default[None]]] = None
metric_type: Optional[Union[Global[MetricType], Variable, Default[None]]] = Field(
default=None, serialization_alias="metricType", validation_alias="metricType"
)


class SpfTimers(BaseModel):
Expand Down Expand Up @@ -243,7 +246,7 @@ class AdvancedOspfv3Attributes(BaseModel):
class BasicOspfv3Attributes(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True, extra="forbid")

router_id: Optional[Union[Global[str], Variable, Default[None]]] = Field(
router_id: Optional[Union[Global[str], Global[IPv4Address], Variable, Default[None]]] = Field(
serialization_alias="routerId", validation_alias="routerId", default=None
)
distance: Optional[Union[Global[int], Variable, Default[int]]] = None
Expand All @@ -264,13 +267,13 @@ class Ospfv3IPv4Parcel(_ParcelBase):

basic: Optional[BasicOspfv3Attributes] = Field(default=None, validation_alias=AliasPath("data", "basic"))
advanced: Optional[AdvancedOspfv3Attributes] = Field(default=None, validation_alias=AliasPath("data", "advanced"))
redistribute: Optional[RedistributedRouteIPv6] = Field(
redistribute: Optional[List[RedistributedRoute]] = Field(
default=None, validation_alias=AliasPath("data", "redistribute")
)
max_metric_router_lsa: Optional[MaxMetricRouterLsa] = Field(
validation_alias=AliasPath("data", "maxMetricRouterLsa"), default=None
)
area: List[Ospfv3IPv6Area] = Field(validation_alias=AliasPath("data", "area"))
area: List[Ospfv3IPv4Area] = Field(validation_alias=AliasPath("data", "area"))


class Ospfv3IPv6Parcel(_ParcelBase):
Expand All @@ -279,7 +282,7 @@ class Ospfv3IPv6Parcel(_ParcelBase):

basic: Optional[BasicOspfv3Attributes] = Field(default=None, validation_alias=AliasPath("data", "basic"))
advanced: Optional[AdvancedOspfv3Attributes] = Field(default=None, validation_alias=AliasPath("data", "advanced"))
redistribute: Optional[RedistributedRouteIPv6] = Field(
redistribute: Optional[List[RedistributedRouteIPv6]] = Field(
default=None, validation_alias=AliasPath("data", "redistribute")
)
max_metric_router_lsa: Optional[MaxMetricRouterLsa] = Field(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@


class InterfaceGRETemplateConverter:
supported_template_types = ("cisco_vpn_interface_gre",)
supported_template_types = ("cisco_vpn_interface_gre", "vpn-vedge-interface-gre")

tunnel_destination_ip4 = "{{gre_tunnelDestination_ip4}}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


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

delete_keys = (
"dead_peer_detection",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
from copy import deepcopy
from typing import List, Optional, Tuple, Union

from catalystwan.api.configuration_groups.parcel import as_global
from catalystwan.models.configuration.feature_profile.common import Prefix
from catalystwan.models.configuration.feature_profile.sdwan.service import Ospfv3IPv4Parcel
from catalystwan.models.configuration.feature_profile.sdwan.service.ospfv3 import (
AdvancedOspfv3Attributes,
BasicOspfv3Attributes,
DefaultArea,
DefaultOriginate,
MaxMetricRouterLsa,
MaxMetricRouterLsaAction,
NormalArea,
NssaArea,
Ospfv3InterfaceParametres,
Ospfv3IPv4Area,
Ospfv3IPv6Parcel,
RedistributedRoute,
RedistributeProtocol,
SpfTimers,
StubArea,
SummaryRoute,
)
from catalystwan.utils.config_migration.converters.exceptions import CatalystwanConverterCantConvertException


class Ospfv3TemplateConverter:
"""
Warning: This class returns a tuple of Ospfv3IPv4Parcel and Ospfv3IPv6Parcel objects,
because the Feature Template has two definitions inside one for IPv4 and one for IPv6.
"""

supported_template_types = ("cisco_ospfv3",)

def create_parcel(
self, name: str, description: str, template_values: dict
) -> Tuple[Ospfv3IPv4Parcel, Ospfv3IPv6Parcel]:
if template_values.get("ospfv3") is None:
raise CatalystwanConverterCantConvertException("Feature Template does not contain OSPFv3 configuration")
ospfv3ipv4 = Ospfv3Ipv4TemplateSubconverter().create_parcel(name, description, template_values)
return ospfv3ipv4 # type: ignore


class Ospfv3Ipv4TemplateSubconverter:
delete_keys = (
"default_information",
"router_id",
"table_map",
"max_metric",
"timers",
"distance_ipv4",
"auto_cost",
"compatible",
)

def create_parcel(self, name: str, description: str, template_values: dict) -> Ospfv3IPv4Parcel:
values = deepcopy(template_values).get("ospfv3", {}).get("address_family", {}).get("ipv4", {})
self.configure_basic_ospf_v3_attributes(values)
self.configure_advanced_ospf_v3_attributes(values)
self.configure_max_metric_router_lsa(values)
self.configure_area(values)
self.configure_redistribute(values)
self.cleanup_keys(values)
return Ospfv3IPv4Parcel(parcel_name=name, parcel_description=description, **values)

def configure_basic_ospf_v3_attributes(self, values: dict) -> None:
distance_configuration = self._get_distance_configuration(values)
basic_values = self._get_basic_values(distance_configuration)
values["basic"] = BasicOspfv3Attributes(router_id=values.get("router_id"), **basic_values)

def _get_distance_configuration(self, values: dict) -> dict:
return values.get("distance_ipv4", {})

def _get_basic_values(self, values: dict) -> dict:
return {
"distance": values.get("distance"),
"external_distance": values.get("ospf", {}).get("external"),
"inter_area_distance": values.get("ospf", {}).get("inter_area"),
"intra_area_distance": values.get("ospf", {}).get("intra_area"),
}

def configure_advanced_ospf_v3_attributes(self, values: dict) -> None:
values["advanced"] = AdvancedOspfv3Attributes(
default_originate=self._configure_originate(values),
spf_timers=self._configure_spf_timers(values),
filter=values.get("table_map", {}).get("filter"),
policy_name=values.get("table_map", {}).get("policy_name"),
reference_bandwidth=values.get("auto_cost", {}).get("reference_bandwidth"),
compatible_rfc1583=values.get("compatible", {}).get("rfc1583"),
)

def _configure_originate(self, values: dict) -> Optional[DefaultOriginate]:
originate = values.get("default_information", {}).get("originate")
if originate is None:
return None
metric = originate.get("metric")
if metric is not None:
metric = as_global(str(metric.value))
return DefaultOriginate(
originate=as_global(True),
always=originate.get("always"),
metric=metric,
metric_type=originate.get("metric_type"),
)

def _configure_spf_timers(self, values: dict) -> Optional[SpfTimers]:
timers = values.get("timers", {}).get("throttle", {}).get("spf")
if timers is None:
return None
return SpfTimers(
delay=timers.get("delay"),
initial_hold=timers.get("initial_hold"),
max_hold=timers.get("max_hold"),
)

def configure_max_metric_router_lsa(self, values: dict) -> None:
router_lsa = values.get("max_metric", {}).get("router_lsa", [])[0] # Payload contains only one item
if router_lsa == []:
return

action = router_lsa.get("ad_type")
if action is not None:
action = as_global(action.value, MaxMetricRouterLsaAction)

values["max_metric_router_lsa"] = MaxMetricRouterLsa(
action=action,
on_startup_time=router_lsa.get("time"),
)

def configure_area(self, values: dict) -> None:
area = values.get("area")
if area is None:
raise CatalystwanConverterCantConvertException("Area is required for OSPFv3")
area_list = []
for area_value in area:
area_list.append(
Ospfv3IPv4Area(
area_number=area_value.get("a_num"),
area_type_config=self._set_area_type_config(area_value),
interfaces=self._set_interfaces(area_value),
ranges=self._set_range(area_value),
)
)
values["area"] = area_list

def _set_area_type_config(self, area_value: dict) -> Optional[Union[StubArea, NssaArea, NormalArea, DefaultArea]]:
if "stub" in area_value:
return StubArea(no_summary=area_value.get("stub", {}).get("no_summary"))
elif "nssa" in area_value:
return NssaArea(no_summary=area_value.get("nssa", {}).get("no_summary"))
elif "normal" in area_value:
return NormalArea()
return DefaultArea()

def _set_interfaces(self, area_value: dict) -> List[Ospfv3InterfaceParametres]:
interfaces = area_value.get("interface", [])
if interfaces == []:
return []
interface_list = []
for interface in interfaces:
if authentication := interface.pop("authentication", None):
area_value["authentication_type"] = authentication.get("type")
interface_list.append(Ospfv3InterfaceParametres(**interface))
return interface_list

def _set_range(self, area_value: dict) -> Optional[List[SummaryRoute]]:
ranges = area_value.get("range")
if ranges is None:
return None
range_list = []
for range_ in ranges:
self._set_summary_prefix(range_)
range_list.append(SummaryRoute(**range_))
return range_list

def _set_summary_prefix(self, range_: dict) -> None:
if address := range_.pop("address"):
range_["network"] = Prefix(
address=as_global(str(address.value.network)), mask=as_global(str(address.value.netmask))
)

def configure_redistribute(self, values: dict) -> None:
redistributes = values.get("redistribute", [])
if redistributes == []:
return None
redistribute_list = []
for redistribute in redistributes:
print(redistribute)
redistribute_list.append(
RedistributedRoute(
protocol=as_global(redistribute.get("protocol").value, RedistributeProtocol),
route_policy=redistribute.get("route_map"),
nat_dia=redistribute.get("dia"),
)
)
values["redistribute"] = redistribute_list

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 @@ -25,6 +25,7 @@
from .ntp import NTPTemplateConverter
from .omp import OMPTemplateConverter
from .ospf import OspfTemplateConverter
from .ospfv3 import Ospfv3TemplateConverter
from .security import SecurityTemplateConverter
from .svi import InterfaceSviTemplateConverter
from .thousandeyes import ThousandEyesTemplateConverter
Expand Down Expand Up @@ -55,6 +56,7 @@
InterfaceEthernetTemplateConverter,
InterfaceIpsecTemplateConverter,
OspfTemplateConverter,
Ospfv3TemplateConverter,
]


Expand Down
4 changes: 4 additions & 0 deletions catalystwan/workflows/config_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def transform(ux1: UX1Config) -> UX2Config:
for ft in ux1.templates.feature_templates:
if ft.template_type in SUPPORTED_TEMPLATE_TYPES:
parcel = create_parcel_from_template(ft)
# if isinstance(parcel, tuple):
# for p in parcel:
# .....
# find uuid in
transformed_parcel = TransformedParcel(
header=TransformHeader(
type=parcel._get_parcel_type(),
Expand Down

0 comments on commit 919e63d

Please sign in to comment.