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

Commit

Permalink
Enhance ux2 config model. Create relations between items that will be…
Browse files Browse the repository at this point in the history
… pushed. Add ERD.
  • Loading branch information
jpkrajewski committed Mar 14, 2024
1 parent 8f9292d commit 5c53427
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 70 deletions.
2 changes: 1 addition & 1 deletion catalystwan/api/configuration_groups/parcel.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def _get_parcel_type(cls) -> str:
field_info = cls.model_fields.get("type_")
if field_info is not None:
return str(field_info.default)
raise CatalystwanException("Field parcel type is not set.")
raise CatalystwanException(f"{cls.__name__} field parcel type is not set.")


class OptionType(str, Enum):
Expand Down
47 changes: 37 additions & 10 deletions catalystwan/models/configuration/config_migration.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
# Copyright 2024 Cisco Systems, Inc. and its affiliates

from typing import List, Union
from uuid import UUID

from pydantic import BaseModel, ConfigDict, Field
from typing_extensions import Annotated

from catalystwan.api.template_api import FeatureTemplateInformation
from catalystwan.api.templates.device_template.device_template import DeviceTemplate
from catalystwan.endpoints.configuration_group import ConfigGroup
from catalystwan.endpoints.configuration_group import ConfigGroupCreationPayload
from catalystwan.models.configuration.feature_profile.common import FeatureProfileCreationPayload
from catalystwan.models.configuration.feature_profile.sdwan.other import AnyOtherParcel
from catalystwan.models.configuration.feature_profile.sdwan.policy_object import AnyPolicyObjectParcel
from catalystwan.models.configuration.feature_profile.sdwan.service import AnyServiceParcel
from catalystwan.models.configuration.feature_profile.sdwan.system import AnySystemParcel
from catalystwan.models.configuration.feature_profile.sdwan.transport import AnyTransportParcel
from catalystwan.models.configuration.topology_group import TopologyGroup
from catalystwan.models.policy import AnyPolicyDefinitionInfo, AnyPolicyListInfo
from catalystwan.models.policy.centralized import CentralizedPolicyInfo
from catalystwan.models.policy.localized import LocalizedPolicyInfo
from catalystwan.models.policy.security import AnySecurityPolicyInfo

AnyParcel = Annotated[
Union[
AnySystemParcel,
AnyPolicyObjectParcel,
],
Union[AnySystemParcel, AnyPolicyObjectParcel, AnyServiceParcel, AnyOtherParcel, AnyTransportParcel],
Field(discriminator="type_"),
]

Expand Down Expand Up @@ -61,21 +62,47 @@ class UX1Config(BaseModel):
templates: UX1Templates = UX1Templates()


class TransformHeader(BaseModel):
type: str
id: UUID
subelements: List[UUID] = []


class TransformedTopologyGroup(BaseModel):
header: TransformHeader
topology_group: TopologyGroup


class TransformedConfigGroup(BaseModel):
header: TransformHeader
config_group: ConfigGroupCreationPayload


class TransformedFeatureProfile(BaseModel):
header: TransformHeader
feature_profile: FeatureProfileCreationPayload


class TransformedParcel(BaseModel):
header: TransformHeader
parcel: AnyParcel


class UX2Config(BaseModel):
# All UX2 Configuration items - Mega Model
model_config = ConfigDict(populate_by_name=True)
topology_groups: List[TopologyGroup] = Field(
topology_groups: List[TransformedTopologyGroup] = Field(
default=[], serialization_alias="topologyGroups", validation_alias="topologyGroups"
)
config_groups: List[ConfigGroup] = Field(
config_groups: List[TransformedConfigGroup] = Field(
default=[], serialization_alias="configurationGroups", validation_alias="configurationGroups"
)
policy_groups: List[ConfigGroup] = Field(
policy_groups: List[TransformedConfigGroup] = Field(
default=[], serialization_alias="policyGroups", validation_alias="policyGroups"
)
feature_profiles: List[FeatureProfileCreationPayload] = Field(
feature_profiles: List[TransformedFeatureProfile] = Field(
default=[], serialization_alias="featureProfiles", validation_alias="featureProfiles"
)
profile_parcels: List[AnyParcel] = Field(
profile_parcels: List[TransformedParcel] = Field(
default=[], serialization_alias="profileParcels", validation_alias="profileParcels"
)
3 changes: 3 additions & 0 deletions catalystwan/models/configuration/docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# UX2 Configuration Model - Entity relationship diagram (ERD)

![alt text](./diagram.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .ucse import UcseParcel

AnyOtherParcel = Annotated[
Union[ThousandEyesParcel, UcseParcel], # noqa: #231
Union[ThousandEyesParcel, UcseParcel],
Field(discriminator="type_"),
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import List, Union

from pydantic import Field
from typing_extensions import Annotated

from .dhcp_server import LanVpnDhcpServerParcel

AnyServiceParcel = Annotated[
Union[LanVpnDhcpServerParcel,], # noqa: E231
Field(discriminator="type_"),
]

__all__ = [
"LanVpnDhcpServerParcel",
"AnyServiceParcel",
]


def __dir__() -> "List[str]":
return list(__all__)
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,7 @@ class MplsInterfaceItem(BaseModel):


class WanRoutingBgpParcel(_ParcelBase):
type_: Literal["bgp"] = Field(default="bgp", exclude=True)
model_config = ConfigDict(
extra="forbid",
populate_by_name=True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ControllerConfig(BaseModel):


class CellularControllerParcel(_ParcelBase):
type_: Literal["cellular-controller"] = Field(default="cellular-controller", exclude=True)
config_type: Default[ConfigTypeValue] = Field(
default=Default(value="non-eSim"), validation_alias=AliasPath("data", "configType")
)
Expand Down
114 changes: 56 additions & 58 deletions catalystwan/workflows/config_migration.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import logging
from typing import Any, Callable, List, Optional
from typing import Callable
from uuid import UUID, uuid4

from pydantic import BaseModel

from catalystwan.api.policy_api import POLICY_LIST_ENDPOINTS_MAP
from catalystwan.endpoints.configuration_group import ConfigGroup, ConfigGroupCreationPayload
from catalystwan.models.configuration.config_migration import UX1Config, UX2Config
from catalystwan.models.configuration.config_migration import (
TransformedConfigGroup,
TransformedFeatureProfile,
TransformedParcel,
TransformHeader,
UX1Config,
UX2Config,
)
from catalystwan.models.configuration.feature_profile.common import FeatureProfileCreationPayload
from catalystwan.session import ManagerSession
from catalystwan.utils.config_migration.converters.feature_template import create_parcel_from_template
from catalystwan.utils.config_migration.converters.policy.policy_lists import convert_all as convert_policy_lists
from catalystwan.utils.config_migration.creators.config_group import ConfigGroupCreator
from catalystwan.utils.config_migration.device_templates import flatten_general_templates

Expand Down Expand Up @@ -72,10 +76,7 @@
"cisco_bgp",
]

FEATURE_PROFILE_TRANSPORT = [
"dhcp",
"cisco_dhcp_server",
]
FEATURE_PROFILE_TRANSPORT = ["dhcp", "cisco_dhcp_server", "dhcp-server"]

FEATURE_PROFILE_OTHER = [
"cisco_thousandeyes",
Expand All @@ -87,91 +88,88 @@ def log_progress(task: str, completed: int, total: int) -> None:
logger.info(f"{task} {completed}/{total}")


class IdModel(BaseModel):
type: str
childs: Optional[List[UUID]] = None
id: UUID
model: Any


def transform2(ux1: UX1Config) -> Any:
def transform(ux1: UX1Config) -> UX2Config:
ux2 = UX2Config()
# Create Feature Profiles and Config Group
for dt in ux1.templates.device_templates:
templates = flatten_general_templates(dt.general_templates)

# Create Feature Profiles
fp_system_uuid = uuid4()
fp_system = IdModel(
type="feature_profile_system",
id=fp_system_uuid,
model=FeatureProfileCreationPayload(
name="system",
transformed_fp_system = TransformedFeatureProfile(
header=TransformHeader(
type="system",
id=fp_system_uuid,
),
feature_profile=FeatureProfileCreationPayload(
name=f"{dt.template_name}_system",
description="system",
),
)
fp_transport_uuid = uuid4()
fp_transport = IdModel(
type="feature_profile_transport",
id=fp_transport_uuid,
model=FeatureProfileCreationPayload(
name="transport",
transformed_fp_transport = TransformedFeatureProfile(
header=TransformHeader(
type="transport",
id=fp_transport_uuid,
),
feature_profile=FeatureProfileCreationPayload(
name=f"{dt.template_name}_transport",
description="transport",
),
)

fp_other_uuid = uuid4()
fp_other = IdModel(
type="feature_profile_other",
id=fp_other_uuid,
model=FeatureProfileCreationPayload(
name="other",
transformed_fp_other = TransformedFeatureProfile(
header=TransformHeader(
type="other",
id=fp_other_uuid,
),
feature_profile=FeatureProfileCreationPayload(
name=f"{dt.template_name}_other",
description="other",
),
)

for template in templates:
# Those feature templates IDs are real UUIDs and are used to map to the feature profiles
if template.templateType in FEATURE_PROFILE_SYSTEM:
fp_system.childs.append(template.templateId)
transformed_fp_system.header.subelements.append(UUID(template.templateId))
elif template.templateType in FEATURE_PROFILE_TRANSPORT:
fp_transport.childs.append(template.templateId)
transformed_fp_transport.header.subelements.append(UUID(template.templateId))
elif template.templateType in FEATURE_PROFILE_OTHER:
fp_other.childs.append(template.templateId)
transformed_fp_other.header.subelements.append(UUID(template.templateId))

cg = IdModel(
type="config_group",
id=uuid4(),
childs=[fp_system_uuid, fp_transport_uuid, fp_other_uuid],
model=ConfigGroupCreationPayload(
transformed_cg = TransformedConfigGroup(
header=TransformHeader(
type="config_group",
id=uuid4(),
subelements=[fp_system_uuid, fp_transport_uuid, fp_other_uuid],
),
config_group=ConfigGroupCreationPayload(
name=dt.template_name,
description=dt.template_description,
solution="sdwan",
profiles=[],
),
)
# Add to UX2
ux2.feature_profiles.append(fp_system)
ux2.feature_profiles.append(fp_transport)
ux2.feature_profiles.append(fp_other)
ux2.config_groups.append(cg)
ux2.feature_profiles.append(transformed_fp_system)
ux2.feature_profiles.append(transformed_fp_transport)
ux2.feature_profiles.append(transformed_fp_other)
ux2.config_groups.append(transformed_cg)

for ft in ux1.templates.feature_templates:
if ft.template_type in SUPPORTED_TEMPLATE_TYPES:
model = create_parcel_from_template(ft)
# Using real UUIDs for the parcels. So creation overhead is reducted and they aleary mapp to the feature profiles
ux2.profile_parcels.append(IdModel(type=model._get_parcel_type(), id=ft.id, model=model))

parcel = create_parcel_from_template(ft)
transformed_parcel = TransformedParcel(
header=TransformHeader(
type=parcel._get_parcel_type(),
id=UUID(ft.id),
),
parcel=parcel,
)
# Add to UX2. We can indentify the parcels as subelements of the feature profiles by the UUIDs
ux2.profile_parcels.append(transformed_parcel)

def transform(ux1: UX1Config) -> UX2Config:
ux2 = UX2Config()
# Feature Templates
for ft in ux1.templates.feature_templates:
if ft.template_type in SUPPORTED_TEMPLATE_TYPES:
model = create_parcel_from_template(ft)
ux2.profile_parcels.append(IdModel(type=model._get_parcel_type(), id=ft.id, model=model))
# Policy Lists
ux2.profile_parcels.extend(convert_policy_lists(ux1.policies.policy_lists).output)
return ux2


Expand Down
Binary file added diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5c53427

Please sign in to comment.