Skip to content

Commit

Permalink
Merge pull request kytos#27 from ajoaoff/issue26
Browse files Browse the repository at this point in the history
Roolback deployment when flow_manager fails.
  • Loading branch information
Antonio Francisco authored Jul 15, 2021
2 parents 5db0489 + b8f857d commit 6da60d7
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 19 deletions.
4 changes: 4 additions & 0 deletions exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ class EVCException(MEFELineException):

class ValidationException(EVCException):
"""Exception for validation errors."""


class FlowModException(MEFELineException):
"""Exception for FlowMod errors."""
35 changes: 23 additions & 12 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from kytos.core.interface import UNI
from kytos.core.link import Link
from napps.kytos.mef_eline import settings
from napps.kytos.mef_eline.exceptions import FlowModException
from napps.kytos.mef_eline.storehouse import StoreHouse
from napps.kytos.mef_eline.utils import emit_event

Expand Down Expand Up @@ -530,13 +531,15 @@ def remove(self):
self.sync()
emit_event(self._controller, 'undeployed', evc_id=self.id)

def remove_current_flows(self):
def remove_current_flows(self, current_path=None):
"""Remove all flows from current path."""
switches = set()

switches.add(self.uni_a.interface.switch)
switches.add(self.uni_z.interface.switch)
for link in self.current_path:
if not current_path:
current_path = self.current_path
for link in current_path:
switches.add(link.endpoint_a.switch)
switches.add(link.endpoint_b.switch)

Expand All @@ -546,7 +549,7 @@ def remove_current_flows(self):
for switch in switches:
self._send_flow_mods(switch, [match], 'delete')

self.current_path.make_vlans_available()
current_path.make_vlans_available()
self.current_path = Path([])
self.deactivate()
self.sync()
Expand Down Expand Up @@ -608,14 +611,20 @@ def deploy_to_path(self, path=None):
else:
use_path = None

if use_path:
self._install_nni_flows(use_path)
self._install_uni_flows(use_path)
elif self.uni_a.interface.switch == self.uni_z.interface.switch:
self._install_direct_uni_flows()
use_path = Path()
else:
log.warn(f"{self} was not deployed. No available path was found.")
try:
if use_path:
self._install_nni_flows(use_path)
self._install_uni_flows(use_path)
elif self.uni_a.interface.switch == self.uni_z.interface.switch:
use_path = Path()
self._install_direct_uni_flows()
else:
log.warn(f"{self} was not deployed. "
"No available path was found.")
return False
except FlowModException:
log.error(f'Error deploying EVC {self} when calling flow_manager.')
self.remove_current_flows(use_path)
return False
self.activate()
self.current_path = use_path
Expand Down Expand Up @@ -743,7 +752,9 @@ def _send_flow_mods(switch, flow_mods, command='flows'):
endpoint = f'{settings.MANAGER_URL}/{command}/{switch.id}'

data = {"flows": flow_mods}
requests.post(endpoint, json=data)
response = requests.post(endpoint, json=data)
if response.status_code >= 400:
raise FlowModException

def get_cookie(self):
"""Return the cookie integer from evc id."""
Expand Down
83 changes: 79 additions & 4 deletions tests/unit/models/test_evc_deploy.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Method to thest EVCDeploy class."""
import sys
from unittest import TestCase
from unittest.mock import Mock, patch
from unittest.mock import MagicMock, Mock, patch

from kytos.core.interface import Interface
from kytos.core.switch import Switch
Expand All @@ -12,6 +12,7 @@

from napps.kytos.mef_eline.models import EVC, Path # NOQA
from napps.kytos.mef_eline.settings import MANAGER_URL # NOQA
from napps.kytos.mef_eline.exceptions import FlowModException # NOQA
from tests.helpers import get_link_mocked,\
get_uni_mocked, get_controller_mock # NOQA

Expand Down Expand Up @@ -92,6 +93,10 @@ def test_send_flow_mods_case1(self, requests_mock):
flow_mods = {"id": 20}
switch = Mock(spec=Switch, id=1)

response = MagicMock()
response.status_code = 201
requests_mock.post.return_value = response

# pylint: disable=protected-access
EVC._send_flow_mods(switch, flow_mods)

Expand All @@ -106,6 +111,9 @@ def test_send_flow_mods_case2(self, requests_mock):
"""Test if you are sending flow_mods."""
flow_mods = {"id": 20}
switch = Mock(spec=Switch, id=1)
response = MagicMock()
response.status_code = 201
requests_mock.post.return_value = response

# pylint: disable=protected-access
EVC._send_flow_mods(switch, flow_mods, command='delete')
Expand Down Expand Up @@ -369,7 +377,11 @@ def test_deploy_successfully(self, *args):
# pylint: disable=too-many-locals
(should_deploy_mock, activate_mock,
install_uni_flows_mock, install_nni_flows, chose_vlans_mock,
log_mock, _, _) = args
log_mock, _, requests_mock) = args

response = MagicMock()
response.status_code = 201
requests_mock.return_value = response

should_deploy_mock.return_value = True
uni_a = get_uni_mocked(interface_port=2, tag_value=82,
Expand Down Expand Up @@ -425,7 +437,11 @@ def test_deploy_fail(self, *args):
# pylint: disable=too-many-locals
(sync_mock, should_deploy_mock, activate_mock, install_uni_flows_mock,
install_nni_flows, choose_vlans_mock,
discover_new_paths, log_mock, _) = args
discover_new_paths, log_mock, requests_mock) = args

response = MagicMock()
response.status_code = 201
requests_mock.return_value = response

uni_a = get_uni_mocked(interface_port=2, tag_value=82,
switch_id="switch_uni_a",
Expand Down Expand Up @@ -462,6 +478,61 @@ def test_deploy_fail(self, *args):
self.assertEqual(sync_mock.call_count, 1)
self.assertFalse(deployed)

@patch('napps.kytos.mef_eline.models.log')
@patch('napps.kytos.mef_eline.models.EVC.discover_new_paths',
return_value=[])
@patch('napps.kytos.mef_eline.models.Path.choose_vlans')
@patch('napps.kytos.mef_eline.models.EVC._install_nni_flows')
@patch('napps.kytos.mef_eline.models.EVC.should_deploy')
@patch('napps.kytos.mef_eline.models.EVC.remove_current_flows')
@patch('napps.kytos.mef_eline.models.EVC.sync')
def test_deploy_error(self, *args):
"""Test if all methods is ignored when the should_deploy is false."""
# pylint: disable=too-many-locals
(sync_mock, remove_current_flows, should_deploy_mock,
install_nni_flows, choose_vlans_mock,
discover_new_paths, log_mock) = args

install_nni_flows.side_effect = FlowModException
should_deploy_mock.return_value = True
uni_a = get_uni_mocked(interface_port=2, tag_value=82,
switch_id="switch_uni_a", is_valid=True)
uni_z = get_uni_mocked(interface_port=3, tag_value=83,
switch_id="switch_uni_z", is_valid=True)

primary_links = [
get_link_mocked(endpoint_a_port=9, endpoint_b_port=10,
metadata={"s_vlan": 5}),
get_link_mocked(endpoint_a_port=11, endpoint_b_port=12,
metadata={"s_vlan": 6})
]

attributes = {
"controller": get_controller_mock(),
"name": "custom_name",
"uni_a": uni_a,
"uni_z": uni_z,
"primary_links": primary_links,
"queue_id": 5
}
# Setup path to deploy
path = Path()
path.append(primary_links[0])
path.append(primary_links[1])

evc = EVC(**attributes)

deployed = evc.deploy_to_path(path)

self.assertEqual(discover_new_paths.call_count, 0)
self.assertEqual(should_deploy_mock.call_count, 1)
self.assertEqual(install_nni_flows.call_count, 1)
self.assertEqual(choose_vlans_mock.call_count, 1)
self.assertEqual(log_mock.error.call_count, 1)
self.assertEqual(sync_mock.call_count, 0)
self.assertEqual(remove_current_flows.call_count, 2)
self.assertFalse(deployed)

@patch('napps.kytos.mef_eline.models.EVC.deploy_to_path')
@patch('napps.kytos.mef_eline.models.EVC.discover_new_paths')
def test_deploy_to_backup_path1(self, discover_new_paths_mocked,
Expand Down Expand Up @@ -512,7 +583,11 @@ def test_deploy_without_path_case1(self, *args):
# pylint: disable=too-many-locals
(discover_new_paths_mocked, should_deploy_mock, activate_mock,
install_uni_flows_mock, install_nni_flows, chose_vlans_mock,
log_mock, _, _) = args
log_mock, _, requests_mock) = args

response = MagicMock()
response.status_code = 201
requests_mock.return_value = response

should_deploy_mock.return_value = False
uni_a = get_uni_mocked(interface_port=2, tag_value=82,
Expand Down
7 changes: 5 additions & 2 deletions tests/unit/models/test_link_protection.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Module to test the LinkProtection class."""
import sys
from unittest import TestCase
from unittest.mock import patch
from unittest.mock import MagicMock, patch
from unittest.mock import Mock

from kytos.core.common import EntityStatus
Expand Down Expand Up @@ -96,9 +96,12 @@ def test_deploy_to_case_1(self, log_mocked):
@patch('napps.kytos.mef_eline.models.Path.status', EntityStatus.UP)
def test_deploy_to_case_2(self, install_uni_flows_mocked,
install_nni_flows_mocked,
deploy_mocked, *_):
deploy_mocked, _, requests_mock):
"""Test deploy with all links up."""
deploy_mocked.return_value = True
response = MagicMock()
response.status_code = 201
requests_mock.return_value = response

primary_path = [
get_link_mocked(status=EntityStatus.UP),
Expand Down
6 changes: 5 additions & 1 deletion tests/unit/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1161,7 +1161,11 @@ def test_delete_schedule_archived(self, *args):
@patch('napps.kytos.mef_eline.main.EVC.as_dict')
def test_update_circuit(self, *args):
"""Test update a circuit circuit."""
(evc_as_dict_mock, uni_from_dict_mock, evc_deploy, *mocks) = args
(evc_as_dict_mock, uni_from_dict_mock, evc_deploy,
*mocks, requests_mock) = args
response = MagicMock()
response.status_code = 201
requests_mock.return_value = response

for mock in mocks:
mock.return_value = True
Expand Down

0 comments on commit 6da60d7

Please sign in to comment.