Skip to content

Commit

Permalink
Merge pull request #571 from kytos-ng/feature/avoid_tags
Browse files Browse the repository at this point in the history
Added support to avoid previous `current_path` tag for redeployment
  • Loading branch information
Alopalao authored Dec 4, 2024
2 parents 71c496a + 582a743 commit 12cb0fa
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 41 deletions.
14 changes: 12 additions & 2 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,13 @@ def delete_metadata(self, request: Request) -> JSONResponse:
def redeploy(self, request: Request) -> JSONResponse:
"""Endpoint to force the redeployment of an EVC."""
circuit_id = request.path_params["circuit_id"]
try_avoid_same_s_vlan = request.query_params.get(
"try_avoid_same_s_vlan", "true"
)
try_avoid_same_s_vlan = try_avoid_same_s_vlan.lower()
if try_avoid_same_s_vlan not in {"true", "false"}:
msg = "Parameter try_avoid_same_s_vlan has an invalid value."
raise HTTPException(400, detail=msg)
log.debug("redeploy /v2/evc/%s/redeploy", circuit_id)
try:
evc = self.circuits[circuit_id]
Expand All @@ -563,9 +570,12 @@ def redeploy(self, request: Request) -> JSONResponse:
deployed = False
if evc.is_enabled():
with evc.lock:
evc.remove_current_flows(sync=False)
path_dict = evc.remove_current_flows(
sync=False,
return_path=try_avoid_same_s_vlan == "true"
)
evc.remove_failover_flows(sync=True)
deployed = evc.deploy()
deployed = evc.deploy(path_dict)
if deployed:
result = {"response": f"Circuit {circuit_id} redeploy received."}
status = 202
Expand Down
44 changes: 30 additions & 14 deletions models/evc.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ def is_using_dynamic_path(self):
return True
return False

def deploy_to_backup_path(self):
def deploy_to_backup_path(self, old_path_dict: dict = None):
"""Deploy the backup path into the datapaths of this circuit.
If the backup_path attribute is valid and up, this method will try to
Expand All @@ -596,17 +596,17 @@ def deploy_to_backup_path(self):

success = False
if self.backup_path.status is EntityStatus.UP:
success = self.deploy_to_path(self.backup_path)
success = self.deploy_to_path(self.backup_path, old_path_dict)

if success:
return True

if self.dynamic_backup_path or self.is_intra_switch():
return self.deploy_to_path()
return self.deploy_to_path(old_path_dict=old_path_dict)

return False

def deploy_to_primary_path(self):
def deploy_to_primary_path(self, old_path_dict: dict = None):
"""Deploy the primary path into the datapaths of this circuit.
If the primary_path attribute is valid and up, this method will try to
Expand All @@ -618,10 +618,10 @@ def deploy_to_primary_path(self):
return True

if self.primary_path.status is EntityStatus.UP:
return self.deploy_to_path(self.primary_path)
return self.deploy_to_path(self.primary_path, old_path_dict)
return False

def deploy(self):
def deploy(self, old_path_dict: dict = None):
"""Deploy EVC to best path.
Best path can be the primary path, if available. If not, the backup
Expand All @@ -630,9 +630,9 @@ def deploy(self):
if self.archived:
return False
self.enable()
success = self.deploy_to_primary_path()
success = self.deploy_to_primary_path(old_path_dict)
if not success:
success = self.deploy_to_backup_path()
success = self.deploy_to_backup_path(old_path_dict)

if success:
emit_event(self._controller, "deployed",
Expand Down Expand Up @@ -708,13 +708,25 @@ def remove_failover_flows(self, exclude_uni_switches=True,
if sync:
self.sync()

def remove_current_flows(self, force=True, sync=True):
def remove_current_flows(
self,
force=True,
sync=True,
return_path=False
) -> dict[str, int]:
"""Remove all flows from current path or path intended for
current path if exists."""
switches = set()
switches, old_path_dict = set(), {}

if not self.current_path and not self.is_intra_switch():
return
return {}

if return_path:
for link in self.current_path:
s_vlan = link.metadata.get("s_vlan")
if s_vlan:
old_path_dict[link.id] = s_vlan.value

current_path = self.current_path
for link in current_path:
switches.add(link.endpoint_a.switch.id)
Expand All @@ -739,10 +751,12 @@ def remove_current_flows(self, force=True, sync=True):
current_path.make_vlans_available(self._controller)
except KytosTagError as err:
log.error(f"Error removing {self} current_path: {err}")
return old_path_dict
self.current_path = Path([])
self.deactivate()
if sync:
self.sync()
return old_path_dict

def remove_path_flows(
self, path=None, force=True
Expand Down Expand Up @@ -887,7 +901,7 @@ def _try_to_activate_inter_evc(self) -> bool:
return True

# pylint: disable=too-many-branches, too-many-statements
def deploy_to_path(self, path=None):
def deploy_to_path(self, path=None, old_path_dict: dict = None):
"""Install the flows for this circuit.
Procedures to deploy:
Expand All @@ -904,10 +918,12 @@ def deploy_to_path(self, path=None):
"""
self.remove_current_flows(sync=False)
use_path = path or Path([])
if not old_path_dict:
old_path_dict = {}
tag_errors = []
if self.should_deploy(use_path):
try:
use_path.choose_vlans(self._controller)
use_path.choose_vlans(self._controller, old_path_dict)
except KytosNoTagAvailableError as e:
tag_errors.append(str(e))
use_path = None
Expand All @@ -916,7 +932,7 @@ def deploy_to_path(self, path=None):
if use_path is None:
continue
try:
use_path.choose_vlans(self._controller)
use_path.choose_vlans(self._controller, old_path_dict)
break
except KytosNoTagAvailableError as e:
tag_errors.append(str(e))
Expand Down
8 changes: 6 additions & 2 deletions models/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ def link_affected_by_interface(self, interface=None):
return link
return None

def choose_vlans(self, controller):
def choose_vlans(self, controller, old_path_dict: dict = None):
"""Choose the VLANs to be used for the circuit."""
old_path_dict = old_path_dict if old_path_dict else {}
for link in self:
tag_value = link.get_next_available_tag(controller, link.id)
tag_value = link.get_next_available_tag(
controller, link.id,
try_avoid_value=old_path_dict.get(link.id)
)
tag = TAG('vlan', tag_value)
link.add_metadata("s_vlan", tag)

Expand Down
6 changes: 6 additions & 0 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ paths:
required: true
schema:
type: string
- name: try_avoid_same_s_vlan
description: Avoid tags from currently deployed current_path.
in: query
schema:
type: boolean
required: false
responses:
'202':
description: Accepted
Expand Down
43 changes: 24 additions & 19 deletions tests/unit/models/test_evc_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ def test_deploy_to_backup_path1(

deployed = evc.deploy_to_backup_path()

deploy_to_path_mocked.assert_called_once_with()
deploy_to_path_mocked.assert_called_once_with(old_path_dict=None)
assert deployed is True

@patch("httpx.post")
Expand Down Expand Up @@ -838,6 +838,20 @@ def test_remove_current_flows(self, *args):
switch_a = Switch("00:00:00:00:00:01")
switch_b = Switch("00:00:00:00:00:02")
switch_c = Switch("00:00:00:00:00:03")
link_a_b = get_link_mocked(
switch_a=switch_a,
switch_b=switch_b,
endpoint_a_port=9,
endpoint_b_port=10,
metadata={"s_vlan": Mock(value=5)},
)
link_b_c = get_link_mocked(
switch_a=switch_b,
switch_b=switch_c,
endpoint_a_port=11,
endpoint_b_port=12,
metadata={"s_vlan": Mock(value=6)},
)

attributes = {
"controller": get_controller_mock(),
Expand All @@ -846,29 +860,20 @@ def test_remove_current_flows(self, *args):
"uni_z": uni_z,
"active": True,
"enabled": True,
"primary_links": [
get_link_mocked(
switch_a=switch_a,
switch_b=switch_b,
endpoint_a_port=9,
endpoint_b_port=10,
metadata={"s_vlan": 5},
),
get_link_mocked(
switch_a=switch_b,
switch_b=switch_c,
endpoint_a_port=11,
endpoint_b_port=12,
metadata={"s_vlan": 6},
),
],
"primary_links": [link_a_b, link_b_c]

}

expected_old_path = {
link_a_b.id: 5,
link_b_c.id: 6
}

evc = EVC(**attributes)

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

old_path = evc.remove_current_flows(return_path=True)
assert old_path == expected_old_path
assert send_flow_mods_mocked.call_count == 1
assert evc.is_active() is False
flows = [
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/models/test_link_protection.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ async def test_handle_link_up_case_2(
current_handle_link_up = evc.handle_link_up(primary_path[0])
assert deploy_mocked.call_count == 0
assert deploy_to_path_mocked.call_count == 1
deploy_to_path_mocked.assert_called_once_with(evc.primary_path)
deploy_to_path_mocked.assert_called_once_with(evc.primary_path, None)
assert current_handle_link_up

@patch("napps.kytos.mef_eline.models.evc.EVCDeploy.deploy")
Expand Down Expand Up @@ -512,7 +512,7 @@ async def test_handle_link_up_case_3(

assert deploy_mocked.call_count == 0
assert deploy_to_path_mocked.call_count == 1
deploy_to_path_mocked.assert_called_once_with(evc.backup_path)
deploy_to_path_mocked.assert_called_once_with(evc.backup_path, None)
assert current_handle_link_up

@patch("napps.kytos.mef_eline.models.evc.EVCDeploy.deploy_to_path")
Expand Down Expand Up @@ -574,7 +574,7 @@ async def test_handle_link_up_case_4(self, *args):
current_handle_link_up = evc.handle_link_up(backup_path[0])

assert deploy_to_path_mocked.call_count == 1
deploy_to_path_mocked.assert_called_once_with()
deploy_to_path_mocked.assert_called_once_with(old_path_dict=None)
assert current_handle_link_up

async def test_handle_link_up_case_5(self):
Expand Down
18 changes: 17 additions & 1 deletion tests/unit/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,9 +828,25 @@ async def test_redeploy_evc(self):
url = f"{self.base_endpoint}/v2/evc/1/redeploy"
response = await self.api_client.patch(url)
evc1.remove_failover_flows.assert_called()
evc1.remove_current_flows.assert_called()
evc1.remove_current_flows.assert_called_with(
sync=False, return_path=True
)
assert response.status_code == 202, response.data

url = f"{self.base_endpoint}/v2/evc/1/redeploy"
url = url + "?try_avoid_same_s_vlan=false"
response = await self.api_client.patch(url)
evc1.remove_current_flows.assert_called_with(
sync=False, return_path=False
)

url = f"{self.base_endpoint}/v2/evc/1/redeploy"
url = url + "?try_avoid_same_s_vlan=True"
response = await self.api_client.patch(url)
evc1.remove_current_flows.assert_called_with(
sync=False, return_path=True
)

async def test_redeploy_evc_disabled(self):
"""Test endpoint to redeploy an EVC."""
evc1 = MagicMock()
Expand Down

0 comments on commit 12cb0fa

Please sign in to comment.