Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4f177e3
Created product type, blocks, workflows and migration files
Sep 30, 2025
0d58be8
Initial working version of NSI
Oct 2, 2025
1d62263
Restructered with working create_form
Oct 3, 2025
780b5b5
Selecting of vlans works
Oct 7, 2025
ff2a9b5
Using CustomRanges
Oct 7, 2025
923543d
Nsistp products types and workflow now works
Oct 7, 2025
48f2a37
Removed unused / unnecessary code
Oct 7, 2025
597efa7
Added single dispatch for subscription description and some minor adj…
Oct 7, 2025
109431d
Added documentation
Oct 7, 2025
94c6604
Remove forms volume from docker-compose
tvdven Oct 8, 2025
a3c4cf7
Netbox Docker image back to v4.4.1
tvdven Oct 8, 2025
0a9c1cc
Removed unused exceptions
Oct 8, 2025
1367bfe
reformat with line lengt 120 again
hanstrompert Oct 9, 2025
548f080
Revert "Removed unused exceptions"
hanstrompert Oct 9, 2025
7b28c3f
move flake8 options from pyproject.toml to .flake8 where they are rec…
hanstrompert Oct 9, 2025
c72c45a
make nsistp description uniform with other descriptions + add node name
hanstrompert Oct 9, 2025
4963ef6
refactor input form field used to get port
hanstrompert Oct 9, 2025
2bccc6a
add form input field validation to modify nsistp + fix subscription d…
hanstrompert Oct 9, 2025
35290f3
remove unneeded AllowedNumberOfNsistpPorts
hanstrompert Oct 9, 2025
846c106
reformat with line length set to 120
hanstrompert Oct 9, 2025
a2cdc7a
improve resource type descriptions + fix nsistp generator yaml
hanstrompert Oct 9, 2025
a1ec70e
address review done by @Mark90
hanstrompert Oct 16, 2025
443f40c
Processed comment Mark
tvdven Oct 17, 2025
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
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 120
270 changes: 145 additions & 125 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "0e8d17ce0f06"
down_revision = "d946c20663d3"
down_revision = "bc54616fefcf"
branch_labels = None
depends_on = None

Expand Down
101 changes: 101 additions & 0 deletions migrations/versions/schema/2025-09-30_a87d11eb8dd1_add_nsistp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Add nsistp product.

Revision ID: a87d11eb8dd1
Revises: 0e8d17ce0f06
Create Date: 2025-09-30 15:50:36.882313

"""

from uuid import uuid4

from alembic import op
from orchestrator.migrations.helpers import create, create_workflow, delete, delete_workflow, ensure_default_workflows
from orchestrator.targets import Target

# revision identifiers, used by Alembic.
revision = "a87d11eb8dd1"
down_revision = "0e8d17ce0f06"
branch_labels = None
depends_on = None

new_products = {
"products": {
"nsistp": {
"product_id": uuid4(),
"product_type": "Nsistp",
"description": "NSISTP",
"tag": "NSISTP",
"status": "active",
"root_product_block": "Nsistp",
"fixed_inputs": {},
},
},
"product_blocks": {
"Nsistp": {
"product_block_id": uuid4(),
"description": "nsistp product block",
"tag": "NSISTP",
"status": "active",
"resources": {
"topology": "Name of the topology this Service Termination Point is exposed in",
"stp_id": "Unique identifier for the Service Termination Point",
"stp_description": "Description of the Service Termination Point",
"is_alias_in": "Inbound port from the other topology in case this STP is part of a SDP",
"is_alias_out": "Outbound port from the other topology in case this STP is part of a SDP",
"expose_in_topology": "Whether to actively expose this STP in the topology",
"bandwidth": "Maximum bandwidth for the combined set of STP (in Mbps)",
},
"depends_on_block_relations": [
"SAP",
],
},
},
"workflows": {},
}

new_workflows = [
{
"name": "create_nsistp",
"target": Target.CREATE,
"is_task": False,
"description": "Create nsistp",
"product_type": "Nsistp",
},
{
"name": "modify_nsistp",
"target": Target.MODIFY,
"is_task": False,
"description": "Modify nsistp",
"product_type": "Nsistp",
},
{
"name": "terminate_nsistp",
"target": Target.TERMINATE,
"is_task": False,
"description": "Terminate nsistp",
"product_type": "Nsistp",
},
{
"name": "validate_nsistp",
"target": Target.VALIDATE,
"is_task": True,
"description": "Validate nsistp",
"product_type": "Nsistp",
},
]


def upgrade() -> None:
conn = op.get_bind()
create(conn, new_products)
for workflow in new_workflows:
create_workflow(conn, workflow)
ensure_default_workflows(conn)


def downgrade() -> None:
conn = op.get_bind()
for workflow in new_workflows:
delete_workflow(conn, workflow["name"])

delete(conn, new_products)
2 changes: 2 additions & 0 deletions products/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from products.product_types.core_link import CoreLink
from products.product_types.l2vpn import L2vpn
from products.product_types.node import Node
from products.product_types.nsistp import Nsistp
from products.product_types.port import Port

SUBSCRIPTION_MODEL_REGISTRY.update(
Expand All @@ -30,5 +31,6 @@
"core link 10G": CoreLink,
"core link 100G": CoreLink,
"l2vpn": L2vpn,
"nsistp": Nsistp,
}
)
57 changes: 57 additions & 0 deletions products/product_blocks/nsistp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Copyright 2019-2023 SURF.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from orchestrator.domain.base import ProductBlockModel
from orchestrator.types import SubscriptionLifecycle
from pydantic import computed_field

from products.product_blocks.sap import SAPBlock, SAPBlockInactive, SAPBlockProvisioning


class NsistpBlockInactive(ProductBlockModel, product_block_name="Nsistp"):
sap: SAPBlockInactive
topology: str | None = None
stp_id: str | None = None
stp_description: str | None = None
is_alias_in: str | None = None
is_alias_out: str | None = None
expose_in_topology: bool | None = None
bandwidth: int | None = None


class NsistpBlockProvisioning(NsistpBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
sap: SAPBlockProvisioning
topology: str
stp_id: str
stp_description: str | None = None
is_alias_in: str | None = None
is_alias_out: str | None = None
expose_in_topology: bool | None = None
bandwidth: int | None = None

@computed_field
@property
def title(self) -> str:
return f"NSISTP {self.stp_id} on {self.sap.title}"


class NsistpBlock(NsistpBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
sap: SAPBlock
topology: str
stp_id: str
stp_description: str | None = None
is_alias_in: str | None = None
is_alias_out: str | None = None
expose_in_topology: bool | None = None
bandwidth: int | None = None
2 changes: 1 addition & 1 deletion products/product_blocks/port.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
# limitations under the License.


from pydantic_forms.types import strEnum
from typing import List

from orchestrator.domain.base import ProductBlockModel
from orchestrator.types import SubscriptionLifecycle
from pydantic import computed_field
from pydantic_forms.types import strEnum

from products.product_blocks.node import NodeBlock, NodeBlockInactive, NodeBlockProvisioning

Expand Down
4 changes: 2 additions & 2 deletions products/product_blocks/sap.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class SAPBlockInactive(ProductBlockModel, product_block_name="SAP"):

class SAPBlockProvisioning(SAPBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
port: PortBlockProvisioning
vlan: int
vlan: int # TODO: refactor to CustomVlanRanges together with L2VPN product and workflow
ims_id: int | None = None

@computed_field # type: ignore[misc]
Expand All @@ -38,5 +38,5 @@ def title(self) -> str:

class SAPBlock(SAPBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
port: PortBlock
vlan: int
vlan: int # TODO: refactor to CustomVlanRanges together with L2VPN product and workflow
ims_id: int
35 changes: 35 additions & 0 deletions products/product_types/nsistp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2019-2023 SURF.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


from orchestrator.domain.base import SubscriptionModel
from orchestrator.types import SubscriptionLifecycle

from products.product_blocks.nsistp import NsistpBlock, NsistpBlockInactive, NsistpBlockProvisioning
from workflows.nsistp.shared.shared import CustomVlanRanges


class NsistpInactive(SubscriptionModel, is_base=True):
nsistp: NsistpBlockInactive


class NsistpProvisioning(NsistpInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
nsistp: NsistpBlockProvisioning


class Nsistp(NsistpProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
nsistp: NsistpBlock

@property
def vlan_range(self) -> CustomVlanRanges:
return self.nsistp.sap.vlan
12 changes: 12 additions & 0 deletions products/services/description.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from products.product_types.core_link import CoreLinkProvisioning
from products.product_types.l2vpn import L2vpnProvisioning
from products.product_types.node import NodeProvisioning
from products.product_types.nsistp import NsistpProvisioning
from products.product_types.port import PortProvisioning
from utils.singledispatch import single_dispatch_base

Expand Down Expand Up @@ -79,3 +80,14 @@ def _(l2vpn: L2vpnProvisioning) -> str:
f"{l2vpn.virtual_circuit.speed} Mbit/s "
f"({'-'.join(sorted(list(set([sap.port.node.node_name for sap in l2vpn.virtual_circuit.saps]))))})"
)


@description.register
def _(nsistp: NsistpProvisioning) -> str:
return (
f"{nsistp.product.tag} "
f"{nsistp.nsistp.stp_id} "
f"topology {nsistp.nsistp.topology} "
f"{nsistp.nsistp.sap.port.node.node_name} "
f"{nsistp.nsistp.bandwidth} Mbit/s"
)
40 changes: 38 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,42 @@ line-length = 120

[tool.isort]
profile = "black"
line_length = 120

[tool.flake8]
max-line-length = 120
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
]

# Same as Black.
line-length = 120
indent-width = 4

# Assume Python 3.9
target-version = "py313"
61 changes: 61 additions & 0 deletions templates/nsistp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2019-2023 SURF.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# This file describes the "L2VPN" product
#
config:
summary_forms: true
name: nsistp
type: Nsistp
tag: NSISTP
description: "NSISTP"
product_blocks:
- name: nsistp
type: Nsistp
tag: NSISTP
description: "nsistp product block"
fields:
- name: sap
type: SAP
description: "NSI STP service access points"
required: provisioning
- name: topology
description: "Name of the topology this Service Termination Point is exposed in"
type: str
required: provisioning
modifiable:
- name: stp_id
description: "Unique identifier for the Service Termination Point"
type: str
required: provisioning
- name: stp_description
description: "Description of the Service Termination Point"
type: str
modifiable:
- name: is_alias_in
description: "Unique identifier for the Service Termination Point"
type: str
modifiable:
- name: is_alias_out
description: "Outbound port from the other topology in case this STP is part of a SDP"
type: str
modifiable:
- name: expose_in_topology
description: "Whether to actively expose this STP in the topology"
type: bool
modifiable:
- name: bandwidth
description: "Maximum bandwidth for the combined set of STP (in Mbps)"
type: int
modifiable:
Loading