Skip to content

Commit 65cb046

Browse files
tvdvenThomas van der Venhanstrompert
authored
[Feature]: add NSISTP product and workflows to example orchestrator (#53)
* Created product type, blocks, workflows and migration files * Initial working version of NSI * Restructered with working create_form * Selecting of vlans works * Using CustomRanges * Nsistp products types and workflow now works * Removed unused / unnecessary code * Added single dispatch for subscription description and some minor adjustments * Added documentation * Remove forms volume from docker-compose Removed forms volume from orchestrator service. * Netbox Docker image back to v4.4.1 Updated the Netbox base image to version 4.4.1. * Removed unused exceptions * reformat with line lengt 120 again * Revert "Removed unused exceptions" This reverts commit 0a9c1cc. At least PortsValueError is needed ... * move flake8 options from pyproject.toml to .flake8 where they are recognized * make nsistp description uniform with other descriptions + add node name * refactor input form field used to get port * add form input field validation to modify nsistp + fix subscription description * remove unneeded AllowedNumberOfNsistpPorts * reformat with line length set to 120 * improve resource type descriptions + fix nsistp generator yaml * address review done by @Mark90 * Processed comment Mark --------- Co-authored-by: Thomas van der Ven <thomas.vanderven@surf.nl> Co-authored-by: Hans Trompert <hans.trompert@surf.nl>
1 parent 432f6d7 commit 65cb046

27 files changed

+1310
-213
lines changed

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
max-line-length = 120

README.md

Lines changed: 145 additions & 125 deletions
Large diffs are not rendered by default.

migrations/versions/schema/2025-08-28_0e8d17ce0f06_reconcile_workflows_l2vpn.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
77
"""
88

9-
import sqlalchemy as sa
109
from alembic import op
1110

1211
# revision identifiers, used by Alembic.
1312
revision = "0e8d17ce0f06"
14-
down_revision = "d946c20663d3"
13+
down_revision = "bc54616fefcf"
1514
branch_labels = None
1615
depends_on = None
1716

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""Add nsistp product.
2+
3+
Revision ID: a87d11eb8dd1
4+
Revises: 0e8d17ce0f06
5+
Create Date: 2025-09-30 15:50:36.882313
6+
7+
"""
8+
9+
from uuid import uuid4
10+
11+
from alembic import op
12+
from orchestrator.migrations.helpers import create, create_workflow, delete, delete_workflow, ensure_default_workflows
13+
from orchestrator.targets import Target
14+
15+
# revision identifiers, used by Alembic.
16+
revision = "a87d11eb8dd1"
17+
down_revision = "0e8d17ce0f06"
18+
branch_labels = None
19+
depends_on = None
20+
21+
new_products = {
22+
"products": {
23+
"nsistp": {
24+
"product_id": uuid4(),
25+
"product_type": "Nsistp",
26+
"description": "NSISTP",
27+
"tag": "NSISTP",
28+
"status": "active",
29+
"root_product_block": "Nsistp",
30+
"fixed_inputs": {},
31+
},
32+
},
33+
"product_blocks": {
34+
"Nsistp": {
35+
"product_block_id": uuid4(),
36+
"description": "nsistp product block",
37+
"tag": "NSISTP",
38+
"status": "active",
39+
"resources": {
40+
"topology": "Name of the topology this Service Termination Point is exposed in",
41+
"stp_id": "Unique identifier for the Service Termination Point",
42+
"stp_description": "Description of the Service Termination Point",
43+
"is_alias_in": "Inbound port from the other topology in case this STP is part of a SDP",
44+
"is_alias_out": "Outbound port from the other topology in case this STP is part of a SDP",
45+
"expose_in_topology": "Whether to actively expose this STP in the topology",
46+
"bandwidth": "Maximum bandwidth for the combined set of STP (in Mbps)",
47+
},
48+
"depends_on_block_relations": [
49+
"SAP",
50+
],
51+
},
52+
},
53+
"workflows": {},
54+
}
55+
56+
new_workflows = [
57+
{
58+
"name": "create_nsistp",
59+
"target": Target.CREATE,
60+
"is_task": False,
61+
"description": "Create nsistp",
62+
"product_type": "Nsistp",
63+
},
64+
{
65+
"name": "modify_nsistp",
66+
"target": Target.MODIFY,
67+
"is_task": False,
68+
"description": "Modify nsistp",
69+
"product_type": "Nsistp",
70+
},
71+
{
72+
"name": "terminate_nsistp",
73+
"target": Target.TERMINATE,
74+
"is_task": False,
75+
"description": "Terminate nsistp",
76+
"product_type": "Nsistp",
77+
},
78+
{
79+
"name": "validate_nsistp",
80+
"target": Target.VALIDATE,
81+
"is_task": True,
82+
"description": "Validate nsistp",
83+
"product_type": "Nsistp",
84+
},
85+
]
86+
87+
88+
def upgrade() -> None:
89+
conn = op.get_bind()
90+
create(conn, new_products)
91+
for workflow in new_workflows:
92+
create_workflow(conn, workflow)
93+
ensure_default_workflows(conn)
94+
95+
96+
def downgrade() -> None:
97+
conn = op.get_bind()
98+
for workflow in new_workflows:
99+
delete_workflow(conn, workflow["name"])
100+
101+
delete(conn, new_products)

products/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from products.product_types.core_link import CoreLink
1818
from products.product_types.l2vpn import L2vpn
1919
from products.product_types.node import Node
20+
from products.product_types.nsistp import Nsistp
2021
from products.product_types.port import Port
2122

2223
SUBSCRIPTION_MODEL_REGISTRY.update(
@@ -30,5 +31,6 @@
3031
"core link 10G": CoreLink,
3132
"core link 100G": CoreLink,
3233
"l2vpn": L2vpn,
34+
"nsistp": Nsistp,
3335
}
3436
)

products/product_blocks/nsistp.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2019-2023 SURF.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
15+
from orchestrator.domain.base import ProductBlockModel
16+
from orchestrator.types import SubscriptionLifecycle
17+
from pydantic import computed_field
18+
19+
from products.product_blocks.sap import SAPBlock, SAPBlockInactive, SAPBlockProvisioning
20+
21+
22+
class NsistpBlockInactive(ProductBlockModel, product_block_name="Nsistp"):
23+
sap: SAPBlockInactive
24+
topology: str | None = None
25+
stp_id: str | None = None
26+
stp_description: str | None = None
27+
is_alias_in: str | None = None
28+
is_alias_out: str | None = None
29+
expose_in_topology: bool | None = None
30+
bandwidth: int | None = None
31+
32+
33+
class NsistpBlockProvisioning(NsistpBlockInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
34+
sap: SAPBlockProvisioning
35+
topology: str
36+
stp_id: str
37+
stp_description: str | None = None
38+
is_alias_in: str | None = None
39+
is_alias_out: str | None = None
40+
expose_in_topology: bool | None = None
41+
bandwidth: int | None = None
42+
43+
@computed_field
44+
@property
45+
def title(self) -> str:
46+
return f"NSISTP {self.stp_id} on {self.sap.title}"
47+
48+
49+
class NsistpBlock(NsistpBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
50+
sap: SAPBlock
51+
topology: str
52+
stp_id: str
53+
stp_description: str | None = None
54+
is_alias_in: str | None = None
55+
is_alias_out: str | None = None
56+
expose_in_topology: bool | None = None
57+
bandwidth: int | None = None

products/product_blocks/port.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
# limitations under the License.
1313

1414

15-
from pydantic_forms.types import strEnum
1615
from typing import List
1716

1817
from orchestrator.domain.base import ProductBlockModel
1918
from orchestrator.types import SubscriptionLifecycle
2019
from pydantic import computed_field
20+
from pydantic_forms.types import strEnum
2121

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

products/product_blocks/sap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class SAPBlockInactive(ProductBlockModel, product_block_name="SAP"):
2727

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

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

3939
class SAPBlock(SAPBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
4040
port: PortBlock
41-
vlan: int
41+
vlan: int # TODO: refactor to CustomVlanRanges together with L2VPN product and workflow
4242
ims_id: int

products/product_types/nsistp.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright 2019-2023 SURF.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
15+
from orchestrator.domain.base import SubscriptionModel
16+
from orchestrator.types import SubscriptionLifecycle
17+
18+
from products.product_blocks.nsistp import NsistpBlock, NsistpBlockInactive, NsistpBlockProvisioning
19+
from workflows.nsistp.shared.shared import CustomVlanRanges
20+
21+
22+
class NsistpInactive(SubscriptionModel, is_base=True):
23+
nsistp: NsistpBlockInactive
24+
25+
26+
class NsistpProvisioning(NsistpInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]):
27+
nsistp: NsistpBlockProvisioning
28+
29+
30+
class Nsistp(NsistpProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]):
31+
nsistp: NsistpBlock
32+
33+
@property
34+
def vlan_range(self) -> CustomVlanRanges:
35+
return self.nsistp.sap.vlan

products/services/description.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from products.product_types.core_link import CoreLinkProvisioning
2222
from products.product_types.l2vpn import L2vpnProvisioning
2323
from products.product_types.node import NodeProvisioning
24+
from products.product_types.nsistp import NsistpProvisioning
2425
from products.product_types.port import PortProvisioning
2526
from utils.singledispatch import single_dispatch_base
2627

@@ -79,3 +80,14 @@ def _(l2vpn: L2vpnProvisioning) -> str:
7980
f"{l2vpn.virtual_circuit.speed} Mbit/s "
8081
f"({'-'.join(sorted(list(set([sap.port.node.node_name for sap in l2vpn.virtual_circuit.saps]))))})"
8182
)
83+
84+
85+
@description.register
86+
def _(nsistp: NsistpProvisioning) -> str:
87+
return (
88+
f"{nsistp.product.tag} "
89+
f"{nsistp.nsistp.stp_id} "
90+
f"topology {nsistp.nsistp.topology} "
91+
f"{nsistp.nsistp.sap.port.node.node_name} "
92+
f"{nsistp.nsistp.bandwidth} Mbit/s"
93+
)

0 commit comments

Comments
 (0)