Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Added uni tag usage #371

Merged
merged 18 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
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
16 changes: 15 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@ All notable changes to the MEF_ELine NApp will be documented in this file.

Added
=====
- Added a UI button for redeploying an EVC
- Added a UI button for redeploying an EVC.
- UNI tag_type are now accepted as string.

Changed
viniarck marked this conversation as resolved.
Show resolved Hide resolved
=======
- UNIs now will use and free tags from ``Interface.available_tags``.
- UNI tag_type is changed to string from 1, 2 and 3 values to ``"vlan"``, ``"vlan_qinq"`` and ``"mpls"`` respectively.

Deprecated
==========
- Deleted emition of ``kytos/.*.link_available_tags`` event. ``kytos/core.interface_tags`` event through Interface takes its place.

General Information
===================
- ``scripts/vlan_type_string.py`` can be used to update the collection ``evcs`` by changing ``tag_type`` from integer to string.

[2023.1.0] - 2023-06-27
***********************
Expand Down
2 changes: 1 addition & 1 deletion db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CircuitScheduleDoc(BaseModel):

class TAGDoc(BaseModel):
"""TAG model"""
tag_type: int
tag_type: str
viniarck marked this conversation as resolved.
Show resolved Hide resolved
value: Union[int, str]

@validator('value')
Expand Down
37 changes: 30 additions & 7 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,11 @@ def create_circuit(self, request: Request) -> JSONResponse:
except ValueError as exception:
raise HTTPException(400, detail=str(exception)) from exception

try:
self._use_uni_tags(evc)
except ValueError as exception:
raise HTTPException(400, detail=str(exception)) from exception

# save circuit
try:
evc.sync()
Expand All @@ -303,6 +308,20 @@ def create_circuit(self, request: Request) -> JSONResponse:
content=map_evc_event_content(evc))
return JSONResponse(result, status_code=status)

@staticmethod
def _use_uni_tags(evc):
uni_a = evc.uni_a
try:
evc._use_uni_vlan(uni_a)
except ValueError as err:
raise err
try:
uni_z = evc.uni_z
evc._use_uni_vlan(uni_z)
except ValueError as err:
evc.make_uni_vlan_available(uni_a)
raise err

@listen_to('kytos/flow_manager.flow.removed')
def on_flow_delete(self, event):
"""Capture delete messages to keep track when flows got removed."""
Expand Down Expand Up @@ -405,6 +424,7 @@ def delete_circuit(self, request: Request) -> JSONResponse:
evc.disable()
self.sched.remove(evc)
evc.archive()
evc.remove_uni_tags()
evc.sync()
log.info("EVC removed. %s", evc)
result = {"response": f"Circuit {circuit_id} removed"}
Expand All @@ -415,7 +435,7 @@ def delete_circuit(self, request: Request) -> JSONResponse:
content=map_evc_event_content(evc))
return JSONResponse(result, status_code=status)

@rest("v2/evc/{circuit_id}/metadata", methods=["GET"])
@rest("/v2/evc/{circuit_id}/metadata", methods=["GET"])
def get_metadata(self, request: Request) -> JSONResponse:
"""Get metadata from an EVC."""
circuit_id = request.path_params["circuit_id"]
Expand All @@ -429,7 +449,7 @@ def get_metadata(self, request: Request) -> JSONResponse:
detail=f"circuit_id {circuit_id} not found."
) from error

@rest("v2/evc/metadata", methods=["POST"])
@rest("/v2/evc/metadata", methods=["POST"])
@validate_openapi(spec)
def bulk_add_metadata(self, request: Request) -> JSONResponse:
"""Add metadata to a bulk of EVCs."""
Expand All @@ -450,7 +470,7 @@ def bulk_add_metadata(self, request: Request) -> JSONResponse:
raise HTTPException(404, detail=fail_evcs)
return JSONResponse("Operation successful", status_code=201)

@rest("v2/evc/{circuit_id}/metadata", methods=["POST"])
@rest("/v2/evc/{circuit_id}/metadata", methods=["POST"])
@validate_openapi(spec)
def add_metadata(self, request: Request) -> JSONResponse:
"""Add metadata to an EVC."""
Expand All @@ -470,7 +490,7 @@ def add_metadata(self, request: Request) -> JSONResponse:
evc.sync()
return JSONResponse("Operation successful", status_code=201)

@rest("v2/evc/metadata/{key}", methods=["DELETE"])
@rest("/v2/evc/metadata/{key}", methods=["DELETE"])
@validate_openapi(spec)
def bulk_delete_metadata(self, request: Request) -> JSONResponse:
"""Delete metada from a bulk of EVCs"""
Expand All @@ -491,7 +511,7 @@ def bulk_delete_metadata(self, request: Request) -> JSONResponse:
raise HTTPException(404, detail=fail_evcs)
return JSONResponse("Operation successful")

@rest("v2/evc/{circuit_id}/metadata/{key}", methods=["DELETE"])
@rest("/v2/evc/{circuit_id}/metadata/{key}", methods=["DELETE"])
def delete_metadata(self, request: Request) -> JSONResponse:
"""Delete metadata from an EVC."""
circuit_id = request.path_params["circuit_id"]
Expand Down Expand Up @@ -946,10 +966,13 @@ def _uni_from_dict(self, uni_dict):
+ f"Could not instantiate interface {interface_id}"
)
raise ValueError(result) from ValueError

tag_convert = {1: "vlan"}
tag_dict = uni_dict.get("tag", None)
if tag_dict:
tag = TAG.from_dict(tag_dict)
tag_type = tag_dict.get("tag_type")
tag_type = tag_convert.get(tag_type, tag_type)
tag_value = tag_dict.get("value")
tag = TAG(tag_type, tag_value)
Alopalao marked this conversation as resolved.
Show resolved Hide resolved
else:
tag = None
uni = UNI(interface, tag)
Expand Down
106 changes: 73 additions & 33 deletions models/evc.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
from napps.kytos.mef_eline.utils import (check_disabled_component,
compare_endpoint_trace,
compare_uni_out_trace, emit_event,
map_dl_vlan, map_evc_event_content,
notify_link_available_tags)
map_dl_vlan, map_evc_event_content)

from .path import DynamicPathManager, Path

Expand Down Expand Up @@ -95,6 +94,7 @@ def __init__(self, controller, **kwargs):
ValueError: raised when object attributes are invalid.

"""
self._controller = controller
self._validate(**kwargs)
super().__init__()

Expand Down Expand Up @@ -140,7 +140,6 @@ def __init__(self, controller, **kwargs):

self.metadata = kwargs.get("metadata", {})

self._controller = controller
self._mongo_controller = controllers.ELineController()

if kwargs.get("active", False):
Expand Down Expand Up @@ -171,6 +170,36 @@ def sync(self, keys: set = None):
return
self._mongo_controller.upsert_evc(self.as_dict())

def _get_unis_use_tags(self, **kwargs) -> (UNI, UNI):
"""Obtain both UNIs (uni_a, uni_z).
If a UNI is changing, verify tags"""
uni_a = kwargs.get("uni_a", None)
uni_a_flag = False
if uni_a and uni_a != self.uni_a:
uni_a_flag = True
try:
self._use_uni_vlan(uni_a)
viniarck marked this conversation as resolved.
Show resolved Hide resolved
except ValueError as err:
raise err

uni_z = kwargs.get("uni_z", None)
if uni_z and uni_z != self.uni_z:
try:
self._use_uni_vlan(uni_z)
self.make_uni_vlan_available(self.uni_z)
except ValueError as err:
if uni_a_flag:
self.make_uni_vlan_available(uni_a)
raise err
else:
uni_z = self.uni_z

if uni_a_flag:
self.make_uni_vlan_available(self.uni_a)
else:
uni_a = self.uni_a
return uni_a, uni_z

def update(self, **kwargs):
"""Update evc attributes.

Expand All @@ -187,8 +216,7 @@ def update(self, **kwargs):

"""
enable, redeploy = (None, None)
uni_a = kwargs.get("uni_a") or self.uni_a
uni_z = kwargs.get("uni_z") or self.uni_z
uni_a, uni_z = self._get_unis_use_tags(**kwargs)
check_disabled_component(uni_a, uni_z)
self._validate_has_primary_or_dynamic(
primary_path=kwargs.get("primary_path"),
Expand Down Expand Up @@ -264,14 +292,6 @@ def _validate(self, **kwargs):
if not isinstance(uni, UNI):
raise ValueError(f"{attribute} is an invalid UNI.")

if not uni.is_valid():
try:
tag = uni.user_tag.value
except AttributeError:
tag = None
message = f"VLAN tag {tag} is not available in {attribute}"
raise ValueError(message)

def _validate_has_primary_or_dynamic(
self,
primary_path=None,
Expand Down Expand Up @@ -399,6 +419,39 @@ def archive(self):
"""Archive this EVC on deletion."""
self.archived = True

def _use_uni_vlan(self, uni: UNI):
"""Use tags from UNI"""
if uni.user_tag is None:
return
tag = uni.user_tag.value
tag_type = uni.user_tag.tag_type
if isinstance(tag, int):
result = uni.interface.use_tags(
self._controller, tag, tag_type
)
if not result:
intf = uni.interface.id
raise ValueError(f"Tag {tag} is not available in {intf}")

def make_uni_vlan_available(self, uni: UNI):
"""Make available tag from UNI"""
if uni.user_tag is None:
return
tag = uni.user_tag.value
tag_type = uni.user_tag.tag_type
if isinstance(tag, int):
result = uni.interface.make_tags_available(
self._controller, tag, tag_type
)
if not result:
intf = uni.interface.id
log.warning(f"Tag {tag} was already available in {intf}")

def remove_uni_tags(self):
"""Remove both UNI usage of a tag"""
self.make_uni_vlan_available(self.uni_a)
self.make_uni_vlan_available(self.uni_z)


# pylint: disable=fixme, too-many-public-methods
class EVCDeploy(EVCBase):
Expand Down Expand Up @@ -603,10 +656,7 @@ def remove_failover_flows(self, exclude_uni_switches=True,
f"Error removing flows from switch {switch.id} for"
f"EVC {self}: {err}"
)
for link in links:
link.make_tag_available(link.get_metadata("s_vlan"))
link.remove_metadata("s_vlan")
notify_link_available_tags(self._controller, link)
self.failover_path.make_vlans_available(self._controller)
self.failover_path = Path([])
if sync:
self.sync()
Expand Down Expand Up @@ -637,9 +687,7 @@ def remove_current_flows(self, current_path=None, force=True):
f"EVC {self}: {err}"
)

current_path.make_vlans_available()
for link in current_path:
notify_link_available_tags(self._controller, link)
current_path.make_vlans_available(self._controller)
self.current_path = Path([])
self.deactivate()
self.sync()
Expand Down Expand Up @@ -676,9 +724,7 @@ def remove_path_flows(self, path=None, force=True):
f"dpid={dpid} evc={self} error={err}"
)

path.make_vlans_available()
for link in path:
notify_link_available_tags(self._controller, link)
path.make_vlans_available(self._controller)

@staticmethod
def links_zipped(path=None):
Expand Down Expand Up @@ -722,19 +768,15 @@ def deploy_to_path(self, path=None): # pylint: disable=too-many-branches
use_path = path
if self.should_deploy(use_path):
try:
use_path.choose_vlans()
for link in use_path:
notify_link_available_tags(self._controller, link)
use_path.choose_vlans(self._controller)
except KytosNoTagAvailableError:
use_path = None
else:
for use_path in self.discover_new_paths():
if use_path is None:
continue
try:
use_path.choose_vlans()
for link in use_path:
notify_link_available_tags(self._controller, link)
use_path.choose_vlans(self._controller)
break
except KytosNoTagAvailableError:
pass
Expand All @@ -750,7 +792,7 @@ def deploy_to_path(self, path=None): # pylint: disable=too-many-branches
self._install_direct_uni_flows()
else:
log.warning(
f"{self} was not deployed. " "No available path was found."
f"{self} was not deployed. No available path was found."
)
return False
except FlowModException as err:
Expand Down Expand Up @@ -791,9 +833,7 @@ def setup_failover_path(self):
if not use_path:
continue
try:
use_path.choose_vlans()
for link in use_path:
notify_link_available_tags(self._controller, link)
use_path.choose_vlans(self._controller)
break
except KytosNoTagAvailableError:
pass
Expand Down
19 changes: 15 additions & 4 deletions models/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from kytos.core import log
from kytos.core.common import EntityStatus, GenericEntity
from kytos.core.interface import TAG
from kytos.core.link import Link
from napps.kytos.mef_eline import settings
from napps.kytos.mef_eline.exceptions import InvalidPath
Expand Down Expand Up @@ -32,16 +33,26 @@ def link_affected_by_interface(self, interface=None):
return link
return None

def choose_vlans(self):
def choose_vlans(self, controller):
"""Choose the VLANs to be used for the circuit."""
for link in self:
tag = link.get_next_available_tag()
tag_value = link.get_next_available_tag(controller, link.id)
tag = TAG('vlan', tag_value)
viniarck marked this conversation as resolved.
Show resolved Hide resolved
link.add_metadata("s_vlan", tag)

def make_vlans_available(self):
def make_vlans_available(self, controller):
"""Make the VLANs used in a path available when undeployed."""
for link in self:
link.make_tag_available(link.get_metadata("s_vlan"))
tag = link.get_metadata("s_vlan")
result_a, result_b = link.make_tags_available(
controller, tag.value, link.id, tag.tag_type
)
if result_a is False:
log.error(f"Tag {tag} was already available in"
f"{link.endpoint_a.id}")
if result_b is False:
log.error(f"Tag {tag} was already available in"
f"{link.endpoint_b.id}")
link.remove_metadata("s_vlan")

def is_valid(self, switch_a, switch_z, is_scheduled=False):
Expand Down
6 changes: 4 additions & 2 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -686,8 +686,10 @@ components:
type: object
properties:
tag_type:
type: integer
format: int32
oneOf:
- type: string
- type: integer
enum: ['vlan', 1]
value:
oneOf:
- type: integer
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
# pip-compile --output-file requirements/dev.txt requirements/dev.in
#
-e git+https://github.com/kytos-ng/python-openflow.git#egg=python-openflow
-e git+https://github.com/kytos-ng/kytos.git#egg=kytos[dev]
-e git+https://github.com/kytos-ng/kytos.git@epic/vlan_pool#egg=kytos[dev]
Alopalao marked this conversation as resolved.
Show resolved Hide resolved
-e .
Loading
Loading