Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c35b0c3
Rewrite of tag_capable + changes to locks to ensure better consistency
Ktmi Aug 14, 2025
e8420b2
Use explicit supported_tag_types
Ktmi Sep 4, 2025
ea698b8
Updated openapi.yml
Ktmi Sep 9, 2025
0ce6464
Allow for transferring tags by deallocating them
Ktmi Sep 10, 2025
af32f0d
Remove min items on tag_ranges
Ktmi Sep 10, 2025
5bb79c4
Merge branch 'merge_scratch' into rebase/tag_capable
Ktmi Oct 23, 2025
9be5649
Merge branch 'master' into rebase/tag_capable
Ktmi Oct 23, 2025
ce7e867
Enable/disable individual interfaces in DB
Ktmi Nov 3, 2025
2cc0e9e
Remove interfaces lock
Ktmi Nov 3, 2025
7167b95
Remove interface locks
Ktmi Nov 3, 2025
f601301
Remove multi tag_lock
Ktmi Nov 3, 2025
e9eafee
Remove links_lock
Ktmi Nov 4, 2025
ddaf470
Use switches lock for get_latest_topology
Ktmi Nov 13, 2025
4d02e2b
Work on some pain points regarding locks.
Ktmi Nov 13, 2025
1c4e757
Streamline lldp updates
Ktmi Nov 24, 2025
d8a0f5f
Fix tests + linter cleanup
Ktmi Dec 22, 2025
5d892c3
Add migration script for default_tag_ranges
Ktmi Jan 22, 2026
a70355b
Update use_tags script to use tag_capable
Ktmi Jan 23, 2026
af5f1e2
Update recover vlans script
Ktmi Jan 23, 2026
c841475
Fix trying to acquire certain locks twice
Ktmi Feb 4, 2026
c5e868f
Fix interface deletion
Ktmi Feb 5, 2026
3838473
Merge branch 'master' into rebase/tag_capable
Ktmi Feb 23, 2026
a7ede81
Updated changelog
Ktmi Feb 23, 2026
bdf704e
Pin kytos branch to `feature/tag_capable` for testing
Ktmi Feb 23, 2026
78a78f9
Fix tests using refering to removed code
Ktmi Feb 23, 2026
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
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ All notable changes to the ``topology`` project will be documented in this file.
[UNRELEASED] - Under development
********************************

Changed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changelog should include the new endpoints

=======
- Added tag pool for ``Links``, separate from ``Interfaces``.

Fixed
=====
- Fixed potential race conditions in multithreaded code.

[2025.2.0] - 2026-02-02
***********************

Expand Down
138 changes: 136 additions & 2 deletions controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
class TopoController:
"""TopoController."""

def __init__(self, get_mongo=lambda: Mongo()) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trivial comment but we've been using this way in other NApps too, do we really need this equivalent change and start deviating from the rest?

def __init__(self, get_mongo=Mongo) -> None:
"""Constructor of TopoController."""
self.mongo = get_mongo()
self.db_client = self.mongo.client
Expand Down Expand Up @@ -156,6 +156,19 @@ def disable_interface(self, interface_id: str) -> Optional[dict]:
interface_id, {"$set": {"enabled": False}}
)

def delete_interface(self, interface_id: str) -> Optional[dict]:
"""Try to delete an interface embedded in a switch."""
switch_id, _, port_num = interface_id.rpartition(":")
port_num = int(port_num)
return self.db.switches.find_one_and_update(
{"_id": switch_id},
{"$unset": {"interfaces.$[iface]": 1}},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this leave a null interface? Did you mean to use $pull instead of $unset?

Can you review this again

array_filters=[{
"iface.port_number": {"$eq": port_num}
}],
return_document=ReturnDocument.AFTER
)

def add_interface_metadata(
self, interface_id: str, metadata: dict
) -> Optional[dict]:
Expand Down Expand Up @@ -189,6 +202,69 @@ def _update_interface(
return_document=ReturnDocument.AFTER,
)

def enable_interfaces(
self,
switch_id: str,
ports: list[int]
):
"""Try to enable several interfaces."""
return self._update_interfaces_of_switch(
switch_id, ports, {"$set": {"enabled": True}}
)

def disable_interfaces(
self,
switch_id: str,
ports: list[int]
):
"""Try to disable several interfaces."""
return self._update_interfaces_of_switch(
switch_id, ports, {"$set": {"enabled": False}}
)

def enable_interfaces_lldp(
self,
switch_id: str,
ports: list[int]
):
"""Try to enable lldp on several interfaces."""
return self._update_interfaces_of_switch(
switch_id, ports, {"$set": {"lldp": True}}
)

def disable_interfaces_lldp(
self,
switch_id: str,
ports: list[int]
):
"""Try to disable lldp on several interfaces."""
return self._update_interfaces_of_switch(
switch_id, ports, {"$set": {"lldp": False}}
)

def _update_interfaces_of_switch(
self,
switch_id: str,
ports: list[int],
update_expr: dict
):
self._set_updated_at(update_expr)
interfaces_expression = {}
for operator, values in update_expr.items():
interfaces_expression[operator] = {
f"interfaces.$[iface].{key}": value
for key, value in values.items()
}

return self.db.switches.find_one_and_update(
{"_id": switch_id},
interfaces_expression,
array_filters=[{
"iface.port_number": {"$in": ports}
}],
return_document=ReturnDocument.AFTER
)

def upsert_link(self, link_id: str, link_dict: dict) -> dict:
"""Update or insert a Link."""
utc_now = datetime.utcnow()
Expand Down Expand Up @@ -299,17 +375,23 @@ def upsert_interface_details(
id_: str,
available_tags: dict[str, list[list[int]]],
tag_ranges: dict[str, list[list[int]]],
default_tag_ranges: dict[str, list[list[int]]],
special_available_tags: dict[str, list[str]],
special_tags: dict[str, list[str]]
special_tags: dict[str, list[str]],
default_special_tags: dict[str, list[str]],
supported_tag_types: list[str],
) -> Optional[dict]:
"""Update or insert interfaces details."""
utc_now = datetime.utcnow()
model = InterfaceDetailDoc(**{
"_id": id_,
"available_tags": available_tags,
"tag_ranges": tag_ranges,
"default_tag_ranges": default_tag_ranges,
"special_available_tags": special_available_tags,
"special_tags": special_tags,
"default_special_tags": default_special_tags,
"supported_tag_types": supported_tag_types,
"updated_at": utc_now
}).model_dump(exclude={"inserted_at"})
updated = self.db.interface_details.find_one_and_update(
Expand Down Expand Up @@ -358,3 +440,55 @@ def delete_interface_from_details(self, intf_id: str) -> Optional[dict]:
return self.db.interface_details.find_one_and_delete(
{"_id": intf_id}
)

# pylint: disable=too-many-arguments
def upsert_link_details(
self,
id_: str,
available_tags: dict[str, list[list[int]]],
tag_ranges: dict[str, list[list[int]]],
default_tag_ranges: dict[str, list[list[int]]],
special_available_tags: dict[str, list[str]],
special_tags: dict[str, list[str]],
default_special_tags: dict[str, list[str]],
supported_tag_types: list[str],
) -> Optional[dict]:
"""Update or insert link details."""
utc_now = datetime.utcnow()
model = InterfaceDetailDoc(**{
"_id": id_,
"available_tags": available_tags,
"tag_ranges": tag_ranges,
"default_tag_ranges": default_tag_ranges,
"special_available_tags": special_available_tags,
"special_tags": special_tags,
"default_special_tags": default_special_tags,
"supported_tag_types": supported_tag_types,
"updated_at": utc_now
}).model_dump(exclude={"inserted_at"})
updated = self.db.link_details.find_one_and_update(
{"_id": id_},
{
"$set": model,
"$setOnInsert": {"inserted_at": utc_now},
},
return_document=ReturnDocument.AFTER,
upsert=True,
)
return updated

def get_links_details(
self, link_ids: List[str]
) -> Optional[dict]:
"""Try to get link details given a list of link ids."""
return self.db.link_details.aggregate(
[
{"$match": {"_id": {"$in": link_ids}}},
]
)

def delete_link_from_details(self, link_id: str) -> Optional[dict]:
"""Delete link from link_details."""
return self.db.link_details.find_one_and_delete(
{"_id": link_id}
)
3 changes: 3 additions & 0 deletions db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,8 @@ class InterfaceDetailDoc(DocumentBaseModel):

available_tags: Dict[str, list[list[int]]]
tag_ranges: Dict[str, list[list[int]]]
default_tag_ranges: dict[str, list[list[int]]]
special_available_tags: Dict[str, list[str]]
special_tags: Dict[str, list[str]]
default_special_tags: dict[str, list[str]]
supported_tag_types: list[str]
Loading