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 3 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
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ Added
=====
- Added a UI button for redeploying an EVC

Changed
viniarck marked this conversation as resolved.
Show resolved Hide resolved
=======
- UNIs now will use and free tags from ``Interface.available_tags``.

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

[2023.1.0] - 2023-06-27
***********************

Expand Down
16 changes: 16 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ def create_circuit(self, request: Request) -> JSONResponse:
except ValidationError as exception:
raise HTTPException(400, detail=str(exception)) from exception

try:
self._use_uni_tags(evc)
viniarck marked this conversation as resolved.
Show resolved Hide resolved
except ValueError as exception:
raise HTTPException(400, detail=str(exception)) from exception

# store circuit in dictionary
self.circuits[evc.id] = evc

Expand All @@ -302,6 +307,16 @@ def create_circuit(self, request: Request) -> JSONResponse:
content=map_evc_event_content(evc))
return JSONResponse(result, status_code=status)

def _use_uni_tags(self, evc):
uni_a = evc.uni_a
evc._use_uni_vlan(uni_a)
viniarck marked this conversation as resolved.
Show resolved Hide resolved
try:
uni_z = evc.uni_z
evc._use_uni_vlan(uni_z)
except ValueError as err:
evc._disuse_uni_vlan(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 @@ -404,6 +419,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 Down
94 changes: 61 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,31 @@ def sync(self, keys: set = None):
return
self._mongo_controller.upsert_evc(self.as_dict())

def _get_unis(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
self._use_uni_vlan(uni_a)
viniarck marked this conversation as resolved.
Show resolved Hide resolved

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

if uni_a_flag:
self._disuse_uni_vlan(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 +211,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(**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 +287,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 +414,32 @@ 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.value
if isinstance(tag, int):
if not uni.interface.use_tags([tag, tag], tag_type):
intf = uni.interface.id
raise ValueError(f"Tag {tag} is not available in {intf}")
uni.interface.notify_interface_tags(self._controller)
viniarck marked this conversation as resolved.
Show resolved Hide resolved

def _disuse_uni_vlan(self, uni: UNI):
viniarck marked this conversation as resolved.
Show resolved Hide resolved
viniarck marked this conversation as resolved.
Show resolved Hide resolved
"""Make available tag from UNI"""
if uni.user_tag is None:
return
tag = uni.user_tag.value
tag_type = uni.user_tag.tag_type.value
if isinstance(tag, int):
uni.interface.make_tags_available([tag, tag], tag_type)
uni.interface.notify_interface_tags(self._controller)

def remove_uni_tags(self):
self._disuse_uni_vlan(self.uni_a)
self._disuse_uni_vlan(self.uni_z)


# pylint: disable=fixme, too-many-public-methods
class EVCDeploy(EVCBase):
Expand Down Expand Up @@ -603,10 +644,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 +675,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 +712,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 +756,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 +780,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 +821,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
13 changes: 9 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,20 @@ 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)
tag = TAG(1, tag_value)
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")
link.make_tag_available(
viniarck marked this conversation as resolved.
Show resolved Hide resolved
controller, tag.value, tag.tag_type.value
)
link.remove_metadata("s_vlan")

def is_valid(self, switch_a, switch_z, is_scheduled=False):
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 .
17 changes: 3 additions & 14 deletions tests/unit/models/test_evc_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,6 @@ def test_deploy_error(self, *args):
assert remove_current_flows.call_count == 2
assert deployed is False

@patch("napps.kytos.mef_eline.models.evc.notify_link_available_tags")
@patch("napps.kytos.mef_eline.models.evc.EVC.get_failover_path_candidates")
@patch("napps.kytos.mef_eline.models.evc.EVC._install_nni_flows")
@patch("napps.kytos.mef_eline.models.evc.EVC._install_uni_flows")
Expand All @@ -715,7 +714,6 @@ def test_setup_failover_path(self, *args):
install_uni_flows_mock,
install_nni_flows_mock,
get_failover_path_candidates_mock,
notify_mock,
) = args

# case1: early return intra switch
Expand All @@ -741,7 +739,6 @@ def test_setup_failover_path(self, *args):
assert evc2.setup_failover_path() is True
remove_path_flows_mock.assert_called_with(["link1", "link2"])
path_mock.choose_vlans.assert_called()
notify_mock.assert_called()
install_nni_flows_mock.assert_called_with(path_mock)
install_uni_flows_mock.assert_called_with(path_mock, skip_in=True)
assert evc2.failover_path == path_mock
Expand Down Expand Up @@ -920,13 +917,12 @@ def test_remove(self, *args):
assert self.evc_deploy.is_enabled() is False

@patch("napps.kytos.mef_eline.controllers.ELineController.upsert_evc")
@patch("napps.kytos.mef_eline.models.evc.notify_link_available_tags")
@patch("napps.kytos.mef_eline.models.evc.EVC._send_flow_mods")
@patch("napps.kytos.mef_eline.models.evc.log.error")
def test_remove_current_flows(self, *args):
"""Test remove current flows."""
# pylint: disable=too-many-locals
(log_error_mock, send_flow_mods_mocked, notify_mock, _) = args
(log_error_mock, send_flow_mods_mocked, _) = args
uni_a = get_uni_mocked(
interface_port=2,
tag_value=82,
Expand Down Expand Up @@ -973,7 +969,6 @@ def test_remove_current_flows(self, *args):

evc.current_path = evc.primary_links
evc.remove_current_flows()
notify_mock.assert_called()

assert send_flow_mods_mocked.call_count == 5
assert evc.is_active() is False
Expand All @@ -992,14 +987,12 @@ def test_remove_current_flows(self, *args):
log_error_mock.assert_called()

@patch("napps.kytos.mef_eline.controllers.ELineController.upsert_evc")
@patch("napps.kytos.mef_eline.models.evc.notify_link_available_tags")
@patch("napps.kytos.mef_eline.models.evc.EVC._send_flow_mods")
@patch("napps.kytos.mef_eline.models.evc.log.error")
def test_remove_failover_flows_exclude_uni_switches(self, *args):
"""Test remove failover flows excluding UNI switches."""
# pylint: disable=too-many-locals
(log_error_mock, send_flow_mods_mocked,
notify_mock, mock_upsert) = args
(log_error_mock, send_flow_mods_mocked, mock_upsert) = args
uni_a = get_uni_mocked(
interface_port=2,
tag_value=82,
Expand Down Expand Up @@ -1044,7 +1037,6 @@ def test_remove_failover_flows_exclude_uni_switches(self, *args):

evc = EVC(**attributes)
evc.remove_failover_flows(exclude_uni_switches=True, sync=True)
notify_mock.assert_called()

assert send_flow_mods_mocked.call_count == 1
flows = [
Expand All @@ -1060,13 +1052,11 @@ def test_remove_failover_flows_exclude_uni_switches(self, *args):
log_error_mock.assert_called()

@patch("napps.kytos.mef_eline.controllers.ELineController.upsert_evc")
@patch("napps.kytos.mef_eline.models.evc.notify_link_available_tags")
@patch("napps.kytos.mef_eline.models.evc.EVC._send_flow_mods")
def test_remove_failover_flows_include_all(self, *args):
"""Test remove failover flows including UNI switches."""
# pylint: disable=too-many-locals
(send_flow_mods_mocked,
notify_mock, mock_upsert) = args
(send_flow_mods_mocked, mock_upsert) = args
uni_a = get_uni_mocked(
interface_port=2,
tag_value=82,
Expand Down Expand Up @@ -1111,7 +1101,6 @@ def test_remove_failover_flows_include_all(self, *args):

evc = EVC(**attributes)
evc.remove_failover_flows(exclude_uni_switches=False, sync=True)
notify_mock.assert_called()

assert send_flow_mods_mocked.call_count == 3
flows = [
Expand Down
Loading
Loading