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

Commit

Permalink
dev: support AppRoute policy (#836)
Browse files Browse the repository at this point in the history
* draft: model

* draft: endpoints

* draft: api layer

* add builder methods to model

* annotate unions with new item type, bump version

* add missing builder methods to traffic-policy parcel

* review fixes
  • Loading branch information
sbasan authored Oct 10, 2024
1 parent 29a9ebd commit b827534
Show file tree
Hide file tree
Showing 54 changed files with 644 additions and 190 deletions.
167 changes: 87 additions & 80 deletions ENDPOINTS.md

Large diffs are not rendered by default.

43 changes: 27 additions & 16 deletions catalystwan/api/policy_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
)
from catalystwan.endpoints.configuration.policy.definition.aip import ConfigurationPolicyAIPDefinition
from catalystwan.endpoints.configuration.policy.definition.amp import ConfigurationPolicyAMPDefinition
from catalystwan.endpoints.configuration.policy.definition.app_route import ConfigurationPolicyAppRouteDefinition
from catalystwan.endpoints.configuration.policy.definition.cflowd import ConfigurationPolicyCflowdDefinition
from catalystwan.endpoints.configuration.policy.definition.control import ConfigurationPolicyControlDefinition
from catalystwan.endpoints.configuration.policy.definition.device_access import (
Expand Down Expand Up @@ -147,6 +148,7 @@
AdvancedMalwareProtectionPolicy,
AdvancedMalwareProtectionPolicyGetResponse,
)
from catalystwan.models.policy.definition.app_route import AppRoutePolicy, AppRoutePolicyGetResponse
from catalystwan.models.policy.definition.cflowd import CflowdPolicy, CflowdPolicyGetResponse
from catalystwan.models.policy.definition.control import ControlPolicy, ControlPolicyGetResponse
from catalystwan.models.policy.definition.device_access import DeviceAccessPolicy, DeviceAccessPolicyGetResponse
Expand Down Expand Up @@ -257,29 +259,30 @@
}

POLICY_DEFINITION_ENDPOINTS_MAP: Mapping[type, type] = {
RuleSet: ConfigurationPolicyRuleSetDefinition,
SecurityGroup: ConfigurationPolicySecurityGroupDefinition,
ZoneBasedFWPolicy: ConfigurationPolicyZoneBasedFirewallDefinition,
TrafficDataPolicy: ConfigurationPolicyDataDefinition,
QoSMapPolicy: ConfigurationPolicyQoSMapDefinition,
RewritePolicy: ConfigurationPolicyRewriteRuleDefinition,
ControlPolicy: ConfigurationPolicyControlDefinition,
VPNMembershipPolicy: ConfigurationPolicyVPNMembershipGroupDefinition,
HubAndSpokePolicy: ConfigurationPolicyHubAndSpokeDefinition,
MeshPolicy: ConfigurationPolicyMeshDefinition,
AclPolicy: ConfigurationPolicyAclDefinition,
AclIPv6Policy: ConfigurationPolicyAclIPv6Definition,
DeviceAccessPolicy: ConfigurationPolicyDeviceAccessDefinition,
DeviceAccessIPv6Policy: ConfigurationPolicyDeviceAccessIPv6Definition,
AclPolicy: ConfigurationPolicyAclDefinition,
AdvancedInspectionProfilePolicy: ConfigurationPolicyAIPDefinition,
AdvancedMalwareProtectionPolicy: ConfigurationPolicyAMPDefinition,
AppRoutePolicy: ConfigurationPolicyAppRouteDefinition,
CflowdPolicy: ConfigurationPolicyCflowdDefinition,
ControlPolicy: ConfigurationPolicyControlDefinition,
DeviceAccessIPv6Policy: ConfigurationPolicyDeviceAccessIPv6Definition,
DeviceAccessPolicy: ConfigurationPolicyDeviceAccessDefinition,
DnsSecurityPolicy: ConfigurationPolicyDnsSecurityDefinition,
HubAndSpokePolicy: ConfigurationPolicyHubAndSpokeDefinition,
IntrusionPreventionPolicy: ConfigurationPolicyIntrusionPreventionDefinition,
MeshPolicy: ConfigurationPolicyMeshDefinition,
QoSMapPolicy: ConfigurationPolicyQoSMapDefinition,
RewritePolicy: ConfigurationPolicyRewriteRuleDefinition,
RoutePolicy: ConfigurationPolicyRouteDefinition,
RuleSet: ConfigurationPolicyRuleSetDefinition,
SecurityGroup: ConfigurationPolicySecurityGroupDefinition,
SslDecryptionPolicy: ConfigurationSslDecryptionDefinition,
SslDecryptionUtdProfilePolicy: ConfigurationSslDecryptionUtdProfileDefinition,
TrafficDataPolicy: ConfigurationPolicyDataDefinition,
UrlFilteringPolicy: ConfigurationPolicyUrlFilteringDefinition,
DnsSecurityPolicy: ConfigurationPolicyDnsSecurityDefinition,
CflowdPolicy: ConfigurationPolicyCflowdDefinition,
RoutePolicy: ConfigurationPolicyRouteDefinition,
VPNMembershipPolicy: ConfigurationPolicyVPNMembershipGroupDefinition,
ZoneBasedFWPolicy: ConfigurationPolicyZoneBasedFirewallDefinition,
}


Expand Down Expand Up @@ -827,6 +830,10 @@ def get(self, type: Type[DeviceAccessPolicy]) -> DataSequence[PolicyDefinitionIn
def get(self, type: Type[DeviceAccessIPv6Policy]) -> DataSequence[PolicyDefinitionInfo]:
...

@overload
def get(self, type: Type[AppRoutePolicy]) -> DataSequence[PolicyDefinitionInfo]:
...

# get by id
@overload
def get(self, type: Type[IntrusionPreventionPolicy], id: UUID) -> IntrusionPreventionPolicyGetResponse:
Expand Down Expand Up @@ -920,6 +927,10 @@ def get(self, type: Type[DeviceAccessPolicy], id: UUID) -> DeviceAccessPolicyGet
def get(self, type: Type[DeviceAccessIPv6Policy], id: UUID) -> DeviceAccessIPv6PolicyGetResponse:
...

@overload
def get(self, type: Type[AppRoutePolicy], id: UUID) -> AppRoutePolicyGetResponse:
...

def get(self, type: Type[AnyPolicyDefinition], id: Optional[UUID] = None) -> Any:
endpoints = self.__get_definition_endpoints_instance(type)
if id is not None:
Expand Down
50 changes: 50 additions & 0 deletions catalystwan/endpoints/configuration/policy/definition/app_route.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2024 Cisco Systems, Inc. and its affiliates

# mypy: disable-error-code="empty-body"

from uuid import UUID

from catalystwan.endpoints import APIEndpoints, delete, get, post, put
from catalystwan.endpoints.configuration.policy.abstractions import PolicyDefinitionEndpoints
from catalystwan.models.policy.definition.app_route import (
AppRoutePolicy,
AppRoutePolicyEditPayload,
AppRoutePolicyGetResponse,
)
from catalystwan.models.policy.policy_definition import (
PolicyDefinitionEditResponse,
PolicyDefinitionId,
PolicyDefinitionInfo,
PolicyDefinitionPreview,
)
from catalystwan.typed_list import DataSequence


class ConfigurationPolicyAppRouteDefinition(APIEndpoints, PolicyDefinitionEndpoints):
@post("/template/policy/definition/approute/")
def create_policy_definition(self, payload: AppRoutePolicy) -> PolicyDefinitionId:
...

@delete("/template/policy/definition/approute/{id}")
def delete_policy_definition(self, id: UUID) -> None:
...

@put("/template/policy/definition/approute/{id}")
def edit_policy_definition(self, id: UUID, payload: AppRoutePolicyEditPayload) -> PolicyDefinitionEditResponse:
...

@get("/template/policy/definition/approute", "data")
def get_definitions(self) -> DataSequence[PolicyDefinitionInfo]:
...

@get("/template/policy/definition/approute/{id}")
def get_policy_definition(self, id: UUID) -> AppRoutePolicyGetResponse:
...

@post("/template/policy/definition/approute/preview")
def preview_policy_definition(self, payload: AppRoutePolicy) -> PolicyDefinitionPreview:
...

@get("/template/policy/definition/approute/preview/{id}")
def preview_policy_definition_by_id(self, id: UUID) -> PolicyDefinitionPreview:
...
2 changes: 2 additions & 0 deletions catalystwan/endpoints/endpoints_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
)
from catalystwan.endpoints.configuration.policy.definition.aip import ConfigurationPolicyAIPDefinition
from catalystwan.endpoints.configuration.policy.definition.amp import ConfigurationPolicyAMPDefinition
from catalystwan.endpoints.configuration.policy.definition.app_route import ConfigurationPolicyAppRouteDefinition
from catalystwan.endpoints.configuration.policy.definition.control import ConfigurationPolicyControlDefinition
from catalystwan.endpoints.configuration.policy.definition.device_access import (
ConfigurationPolicyDeviceAccessDefinition,
Expand Down Expand Up @@ -151,6 +152,7 @@ def __init__(self, session: ManagerSession):
self.acl_ipv6 = ConfigurationPolicyAclIPv6Definition(session)
self.advanced_inspection_profile = ConfigurationPolicyAIPDefinition(session)
self.advanced_malware_protection = ConfigurationPolicyAMPDefinition(session)
self.app_route = ConfigurationPolicyAppRouteDefinition(session)
self.control = ConfigurationPolicyControlDefinition(session)
self.data = ConfigurationPolicyDataDefinition(session)
self.device_access = ConfigurationPolicyDeviceAccessDefinition(session)
Expand Down
4 changes: 2 additions & 2 deletions catalystwan/models/configuration/feature_profile/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ def set_to_dhcp(


class NextHopContainer(BaseModel):
next_hop: List[NextHop] = Field(default=[], serialization_alias="nextHop", validation_alias="nextHop")
next_hop: List[NextHop] = Field(default_factory=list, serialization_alias="nextHop", validation_alias="nextHop")


class Ipv6StaticRouteNull0(BaseModel):
Expand Down Expand Up @@ -687,7 +687,7 @@ class MultilinkControllerTxExList(BaseModel):
)

channel_group: List[ChannelGroup] = Field(
default=[],
default_factory=list,
validation_alias="channelGroup",
serialization_alias="channelGroup",
description="Channel Group List",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ class Ipv6AclParcel(_ParcelBase):
default=Default[Literal["drop"]](value="drop"), validation_alias=AliasPath("data", "defaultAction")
)
sequences: List[Sequence] = Field(
default=[], validation_alias=AliasPath("data", "sequences"), description="Access Control List"
default_factory=list, validation_alias=AliasPath("data", "sequences"), description="Access Control List"
)

def set_default_action(self, action: AcceptDropActionType):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ class TrafficClassMatch(BaseModel):

class DscpMatch(BaseModel):
model_config = ConfigDict(populate_by_name=True, extra="forbid")
dscp: Global[List[int]] = Field(default=None)
dscp: Global[int] = Field(default=None)


class PacketLengthMatch(BaseModel):
Expand Down Expand Up @@ -538,6 +538,36 @@ class SlaClassAction(BaseModel):
default=None, validation_alias="slaClass", serialization_alias="slaClass", description="slaClass"
)

@staticmethod
def from_params(
sla_name: Optional[UUID] = None,
preferred_color: Optional[List[TLOCColor]] = None,
preferred_color_group: Optional[UUID] = None,
preferred_remote_color: Optional[List[TLOCColor]] = None,
remote_color_restrict: Optional[bool] = None,
strict: Optional[bool] = None,
fallback_to_best_path: Optional[bool] = None,
) -> "SlaClassAction":
action = SlaClassAction()
action.sla_class = []
if sla_name:
action.sla_class.append(SlaClass(sla_name=RefIdItem.from_uuid(sla_name)))
if preferred_color:
action.sla_class.append(SlaClass(preferred_color=Global[List[TLOCColor]](value=preferred_color)))
if preferred_color_group:
action.sla_class.append(SlaClass(preferred_color_group=RefIdItem.from_uuid(preferred_color_group)))
if preferred_remote_color:
action.sla_class.append(
SlaClass(preferred_remote_color=Global[List[TLOCColor]](value=preferred_remote_color))
)
if remote_color_restrict is not None:
action.sla_class.append(SlaClass(remote_color_restrict=Global[bool](value=remote_color_restrict)))
if strict is not None:
action.sla_class.append(SlaClass(strict=Global[bool](value=strict)))
if fallback_to_best_path is not None:
action.sla_class.append(SlaClass(fallback_to_best_path=Global[bool](value=fallback_to_best_path)))
return action


class BackupSlaPreferredColorAction(BaseModel):
model_config = ConfigDict(populate_by_name=True, extra="forbid")
Expand Down Expand Up @@ -767,7 +797,7 @@ def match_traffic_class(self, traffic_class: TrafficClass):
entry = TrafficClassMatch(traffic_class=as_global(traffic_class, TrafficClass))
self._match(entry)

def match_dscp(self, dscp: List[int]):
def match_dscp(self, dscp: int):
entry = DscpMatch(dscp=as_global(dscp))
self._match(entry)

Expand Down Expand Up @@ -952,8 +982,10 @@ def associate_appqoe_optimization_action(
action = AppqoeOptimizationAction(appqoe_optimization=appqoe_optimization)
self._insert_action(action)

def associate_backup_sla_preferred_color_action(self) -> None:
pass # TODO
def associate_backup_sla_preferred_color_action(self, tloc_colors: List[TLOCColor]) -> None:
self._insert_action(
BackupSlaPreferredColorAction(backup_sla_preferred_color=Global[List[TLOCColor]](value=tloc_colors))
)

def associate_cflowd_action(self, cflowd: bool) -> None:
self._insert_action(CflowdAction(cflowd=as_global(cflowd)))
Expand All @@ -962,7 +994,7 @@ def associate_cloud_probe_action(self) -> None:
pass # TODO

def associate_cloud_saas_action(self) -> None:
pass # TODO
self._insert_action(CloudSaasAction(cloud_saas=as_global(True)))

def associate_count_action(self, count: str) -> None:
self._insert_action(CountAction(count=as_global(count)))
Expand Down Expand Up @@ -1006,8 +1038,26 @@ def associate_set_action(self) -> None:
def associate_sig_action(self) -> None:
self._insert_action(SigAction(sig=as_global(True)))

def associate_sla_class_action(self) -> None:
pass # TODO
def associate_sla_class_action(
self,
sla_name: Optional[UUID] = None,
preferred_color: Optional[List[TLOCColor]] = None,
preferred_color_group: Optional[UUID] = None,
preferred_remote_color: Optional[List[TLOCColor]] = None,
remote_color_restrict: Optional[bool] = None,
strict: Optional[bool] = None,
fallback_to_best_path: Optional[bool] = None,
) -> None:
action = SlaClassAction.from_params(
sla_name=sla_name,
preferred_color=preferred_color,
preferred_color_group=preferred_color_group,
preferred_remote_color=preferred_remote_color,
remote_color_restrict=remote_color_restrict,
strict=strict,
fallback_to_best_path=fallback_to_best_path,
)
self._insert_action(action)

def associate_sse_action(self) -> None:
pass # TODO
Expand All @@ -1019,7 +1069,7 @@ class TrafficPolicyParcel(_ParcelBase):
default=None, validation_alias=AliasPath("data", "dataDefaultAction")
)
has_cor_via_sig: Optional[Global[bool]] = Field(default=None, validation_alias=AliasPath("data", "hasCorViaSig"))
sequences: List[Sequence] = Field(default=[], validation_alias=AliasPath("data", "sequences"))
sequences: List[Sequence] = Field(default_factory=list, validation_alias=AliasPath("data", "sequences"))
simple_flow: Optional[Global[bool]] = Field(default=None, validation_alias=AliasPath("data", "simpleFlow"))
target: TrafficPolicyTarget = Field(validation_alias=AliasPath("data", "target"))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ class NgFirewallSequence(BaseModel):
)
match: Match
actions: List[Union[LogAction, AipAction]] = Field(
default=[], validation_alias="actions", min_length=0, max_length=2, serialization_alias="actions"
default_factory=list, validation_alias="actions", min_length=0, max_length=2, serialization_alias="actions"
)
disable_sequence: Global[bool] = Field(
default=Global[bool](value=False),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class PolicyParcel(_ParcelBase):
description="Set the parcel description",
)
assembly: List[Union[NgFirewallContainer, SslDecryption, AdvancedInspectionProfile]] = Field(
default=[], validation_alias=AliasPath("data", "assembly")
default_factory=list, validation_alias=AliasPath("data", "assembly")
)

settings: Optional[PolicySettings] = Field(default=None, validation_alias=AliasPath("data", "settings"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class AppProbeMapItem(BaseModel):

class AppProbeEntry(BaseModel):
model_config = ConfigDict(populate_by_name=True)
map: List[AppProbeMapItem] = Field(default=[])
map: List[AppProbeMapItem] = Field(default_factory=list)
forwarding_class: Union[Global[str], RefIdItem] = Field(
validation_alias="forwardingClass",
serialization_alias="forwardingClass",
Expand All @@ -28,7 +28,7 @@ class AppProbeEntry(BaseModel):
class AppProbeParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["app-probe"] = Field(default="app-probe", exclude=True)
entries: List[AppProbeEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[AppProbeEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def _insert_entry(self, entry: AppProbeEntry) -> None:
if self.entries:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ApplicationListParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["app-list"] = Field(default="app-list", exclude=True)
entries: List[Union[ApplicationListEntry, ApplicationFamilyListEntry]] = Field(
default=[], validation_alias=AliasPath("data", "entries")
default_factory=list, validation_alias=AliasPath("data", "entries")
)

def add_application(self, application: str):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AsPathParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["as-path"] = Field(default="as-path", exclude=True)
as_path_list_num: Global[int] = Field(validation_alias=AliasPath("data", "asPathListNum"))
entries: List[AsPathEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[AsPathEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_as_path(self, as_path: str):
self.entries.append(AsPathEntry(as_path=as_global(as_path)))
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ColorEntry(BaseModel):
class ColorParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["color"] = Field(default="color", exclude=True)
entries: List[ColorEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[ColorEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_color(self, color: TLOCColor):
self.entries.append(ColorEntry(color=as_global(color, TLOCColor)))
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def from_string(cls, entry: str):
class ExtendedCommunityParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["ext-community"] = Field(default="ext-community", exclude=True)
entries: List[ExtendedCommunityEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[ExtendedCommunityEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_site_of_origin_community(self, ip_address: IPv4Address, port: int):
entry = f"soo {ip_address}:{port}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def check_burst(cls, queue: Global):
class FowardingClassParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["class"] = Field(default="class", exclude=True)
entries: List[FowardingClassQueueEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[FowardingClassQueueEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_queue(self, queue: int):
self.entries.append(FowardingClassQueueEntry(queue=as_global(str(queue))))
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class IPv6DataPrefixEntry(BaseModel):
class IPv6DataPrefixParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["data-ipv6-prefix"] = Field(default="data-ipv6-prefix", exclude=True)
entries: List[IPv6DataPrefixEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[IPv6DataPrefixEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_prefix(self, ipv6_network: IPv6Interface):
self.entries.append(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class IPv6PrefixListEntry(BaseModel):
class IPv6PrefixListParcel(_ParcelBase):
model_config = ConfigDict(populate_by_name=True)
type_: Literal["ipv6-prefix"] = Field(default="ipv6-prefix", exclude=True)
entries: List[IPv6PrefixListEntry] = Field(default=[], validation_alias=AliasPath("data", "entries"))
entries: List[IPv6PrefixListEntry] = Field(default_factory=list, validation_alias=AliasPath("data", "entries"))

def add_prefix(self, ipv6_network: IPv6Interface, ge: Optional[int] = None, le: Optional[int] = None):
self.entries.append(
Expand Down
Loading

0 comments on commit b827534

Please sign in to comment.