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: Allow creation for "any" and "untagged" as EVCs #258

Merged
merged 36 commits into from
Mar 14, 2023
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
1080ed5
Removed required property from UNI tag
Alopalao Feb 2, 2023
d6772b3
Added support to str in tag.value
Alopalao Feb 7, 2023
fc66391
Merge branch 'master' into untagged_uni
Alopalao Feb 7, 2023
165c431
Extended changes to more flow types
Alopalao Feb 8, 2023
0f2aa8c
Added new static method _get_value_from_uni_tag()
Alopalao Feb 8, 2023
a5369f0
Fixed linter
Alopalao Feb 8, 2023
eee89e8
Reversed tox.ini
Alopalao Feb 8, 2023
31e0cb1
Added support for intra-switch EVC
Alopalao Feb 9, 2023
eac6bf0
Added more possible strings to `value` from TagDoc
Alopalao Feb 15, 2023
4cadff0
Added support for `untagged`
Alopalao Feb 21, 2023
6f82d45
Corrected EVC combinations
Alopalao Mar 1, 2023
8115423
Fixed linter
Alopalao Mar 1, 2023
bb7fceb
Removed required property from UNI tag
Alopalao Feb 2, 2023
4c768cc
Added support to str in tag.value
Alopalao Feb 7, 2023
81d5ae6
Extended changes to more flow types
Alopalao Feb 8, 2023
6d91bd7
Added new static method _get_value_from_uni_tag()
Alopalao Feb 8, 2023
60626a3
Fixed linter
Alopalao Feb 8, 2023
82a9869
Reversed tox.ini
Alopalao Feb 8, 2023
ebd6dcc
Added support for intra-switch EVC
Alopalao Feb 9, 2023
b4bbe20
Added more possible strings to `value` from TagDoc
Alopalao Feb 15, 2023
9bace29
Added support for `untagged`
Alopalao Feb 21, 2023
df23b06
Corrected EVC combinations
Alopalao Mar 1, 2023
ab731a4
Fixed linter
Alopalao Mar 1, 2023
e3b994b
Merge branch 'untagged_uni' of https://github.com/Alopalao/mef_eline …
Alopalao Mar 1, 2023
72c25d9
Added support to str in tag.value
Alopalao Feb 7, 2023
d877469
Added new static method _get_value_from_uni_tag()
Alopalao Feb 8, 2023
17acb03
Added more possible strings to `value` from TagDoc
Alopalao Feb 15, 2023
4c9ffc9
Added support for `untagged`
Alopalao Feb 21, 2023
4a407e3
Corrected EVC combinations
Alopalao Mar 1, 2023
e976108
Merge branch 'untagged_uni' of https://github.com/Alopalao/mef_eline …
Alopalao Mar 6, 2023
59258b8
Reduced if statements
Alopalao Mar 6, 2023
ad2e558
Changed priorities depending on new cases
Alopalao Mar 7, 2023
b5e4fbe
Reversed string vlan, temporal
Alopalao Mar 7, 2023
aa791e6
Polished code
Alopalao Mar 10, 2023
128f80c
Added new dictionary to EVC, `special_cases`
Alopalao Mar 10, 2023
5c98c93
Added exceptio handling in update()
Alopalao Mar 13, 2023
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
2 changes: 2 additions & 0 deletions controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Dict, Optional

import pymongo
from pydantic import ValidationError
from pymongo.collection import ReturnDocument
from pymongo.errors import AutoReconnect
from tenacity import retry_if_exception_type, stop_after_attempt, wait_random
Expand Down Expand Up @@ -77,6 +78,7 @@ def upsert_evc(self, evc: Dict) -> Optional[Dict]:
**{"_id": evc["id"]}
}
)

updated = self.db.evcs.find_one_and_update(
{"_id": evc["id"]},
{
Expand Down
15 changes: 12 additions & 3 deletions db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# pylint: disable=no-self-argument,no-name-in-module

from datetime import datetime
from typing import Dict, List, Literal, Optional
from typing import Dict, List, Literal, Optional, Union

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, validator


class DocumentBaseModel(BaseModel):
Expand Down Expand Up @@ -38,7 +38,16 @@ class CircuitScheduleDoc(BaseModel):
class TAGDoc(BaseModel):
"""TAG model"""
tag_type: int
value: int
value: Union[int, str]

@validator('value')
def validate_value(cls, value):
"""Validate value when is a string"""
if isinstance(value, int):
return value
if isinstance(value, str) and value in ("any", "untagged"):
return value
raise ValueError("value as string allows 'any' and 'untagged'")


class UNIDoc(BaseModel):
Expand Down
10 changes: 7 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from threading import Lock

from flask import jsonify, request
from pydantic import ValidationError
from werkzeug.exceptions import (BadRequest, Conflict, Forbidden,
MethodNotAllowed, NotFound,
UnsupportedMediaType)
Expand Down Expand Up @@ -229,12 +230,15 @@ def create_circuit(self, data):
except ValueError as exception:
raise BadRequest(str(exception)) from exception

# save circuit
try:
evc.sync()
except ValidationError as exception:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
raise BadRequest(str(exception)) from exception

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

# save circuit
evc.sync()

# Schedule the circuit deploy
self.sched.add(evc)

Expand Down
125 changes: 82 additions & 43 deletions models/evc.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,10 @@ def _validate(self, **kwargs):
raise ValueError(f"{attribute} is an invalid UNI.")

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

Expand Down Expand Up @@ -810,41 +813,50 @@ def get_failover_flows(self):

def _prepare_direct_uni_flows(self):
"""Prepare flows connecting two UNIs for intra-switch EVC."""
vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
vlan_a = self._get_value_from_uni_tag(self.uni_a)
vlan_z = self._get_value_from_uni_tag(self.uni_z)

is_EVPL = (vlan_a is not None)
flow_mod_az = self._prepare_flow_mod(
self.uni_a.interface, self.uni_z.interface,
self.queue_id, is_EVPL
self.queue_id, vlan_a
)
is_EVPL = (vlan_z is not None)
flow_mod_za = self._prepare_flow_mod(
self.uni_z.interface, self.uni_a.interface,
self.queue_id, is_EVPL
self.queue_id, vlan_z
)

if vlan_a and vlan_z:
if vlan_a is not None:
flow_mod_az["match"]["dl_vlan"] = vlan_a

if vlan_z is not None:
flow_mod_za["match"]["dl_vlan"] = vlan_z

if vlan_z not in {None, "4096/4096", 0}:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
flow_mod_az["actions"].insert(
0, {"action_type": "set_vlan", "vlan_id": vlan_z}
)
if not vlan_a:
flow_mod_az["actions"].insert(
0, {"action_type": "push_vlan", "tag_type": "c"}
)

if vlan_a not in {None, "4096/4096", 0}:
flow_mod_za["actions"].insert(
0, {"action_type": "set_vlan", "vlan_id": vlan_a}
)
elif vlan_a:
flow_mod_az["match"]["dl_vlan"] = vlan_a
0, {"action_type": "set_vlan", "vlan_id": vlan_a}
)
if not vlan_z:
flow_mod_za["actions"].insert(
0, {"action_type": "push_vlan", "tag_type": "c"}
)
if vlan_z == 0:
flow_mod_az["actions"].insert(0, {"action_type": "pop_vlan"})

elif vlan_a == "4096/4096" and vlan_z == 0:
flow_mod_az["actions"].insert(0, {"action_type": "pop_vlan"})
flow_mod_za["actions"].insert(
0, {"action_type": "set_vlan", "vlan_id": vlan_a}
)
elif vlan_z:
flow_mod_za["match"]["dl_vlan"] = vlan_z

elif vlan_a == 0 and vlan_z:
flow_mod_za["actions"].insert(0, {"action_type": "pop_vlan"})
flow_mod_az["actions"].insert(
0, {"action_type": "set_vlan", "vlan_id": vlan_z}
)

return (
self.uni_a.interface.switch.id, [flow_mod_az, flow_mod_za]
)
Expand Down Expand Up @@ -895,6 +907,19 @@ def _install_nni_flows(self, path=None):
for dpid, flows in self._prepare_nni_flows(path).items():
self._send_flow_mods(dpid, flows)

@staticmethod
def _get_value_from_uni_tag(uni):
"""Returns the value from tag. In case of any and untagged
it should return 4096/4096 and 0 respectively"""
special = {"any": "4096/4096", "untagged": 0}

if uni.user_tag:
value = uni.user_tag.value
if isinstance(value, str):
return special.get(value, value)
return value
return None

def _prepare_uni_flows(self, path=None, skip_in=False, skip_out=False):
"""Prepare flows to install UNIs."""
uni_flows = {}
Expand All @@ -903,10 +928,10 @@ def _prepare_uni_flows(self, path=None, skip_in=False, skip_out=False):
return uni_flows

# Determine VLANs
in_vlan_a = self.uni_a.user_tag.value if self.uni_a.user_tag else None
in_vlan_a = self._get_value_from_uni_tag(self.uni_a)
out_vlan_a = path[0].get_metadata("s_vlan").value

in_vlan_z = self.uni_z.user_tag.value if self.uni_z.user_tag else None
in_vlan_z = self._get_value_from_uni_tag(self.uni_z)
out_vlan_z = path[-1].get_metadata("s_vlan").value

# Flows for the first UNI
Expand Down Expand Up @@ -1001,8 +1026,19 @@ def get_id_from_cookie(cookie):
evc_id = cookie - (settings.COOKIE_PREFIX << 56)
return f"{evc_id:x}".zfill(14)

@staticmethod
def get_priority(vlan):
"""Return priority value depending on vlan value"""
if vlan not in {"4096/4096", None, 0}:
return settings.EVPL_SB_PRIORITY
if vlan == 0:
return settings.UNTAGGED_SB_PRIORITY
if vlan == "4096/4096":
return settings.ANY_SB_PRIORITY
return settings.EPL_SB_PRIORITY

def _prepare_flow_mod(self, in_interface, out_interface,
queue_id=None, is_EVPL=True):
queue_id=None, vlan=True):
"""Prepare a common flow mod."""
default_actions = [
{"action_type": "output", "port": out_interface.port_number}
Expand All @@ -1020,10 +1056,7 @@ def _prepare_flow_mod(self, in_interface, out_interface,
if self.sb_priority:
flow_mod["priority"] = self.sb_priority
else:
if is_EVPL:
flow_mod["priority"] = settings.EVPL_SB_PRIORITY
else:
flow_mod["priority"] = settings.EPL_SB_PRIORITY
flow_mod["priority"] = self.get_priority(vlan)
return flow_mod

def _prepare_nni_flow(self, *args, queue_id=None):
Expand All @@ -1033,13 +1066,11 @@ def _prepare_nni_flow(self, *args, queue_id=None):
in_interface, out_interface, queue_id
)
flow_mod["match"]["dl_vlan"] = in_vlan

new_action = {"action_type": "set_vlan", "vlan_id": out_vlan}
flow_mod["actions"].insert(0, new_action)

return flow_mod

# pylint: disable=too-many-arguments
def _prepare_push_flow(self, *args, queue_id=None):
"""Prepare push flow.

Expand All @@ -1056,35 +1087,43 @@ def _prepare_push_flow(self, *args, queue_id=None):
"""
# assign all arguments
in_interface, out_interface, in_vlan, out_vlan, new_c_vlan = args
is_EVPL = (in_vlan is not None)
flow_mod = self._prepare_flow_mod(
in_interface, out_interface, queue_id, is_EVPL
in_interface, out_interface, queue_id, in_vlan
)

# the service tag must be always pushed
new_action = {"action_type": "set_vlan", "vlan_id": out_vlan}
flow_mod["actions"].insert(0, new_action)

new_action = {"action_type": "push_vlan", "tag_type": "s"}
flow_mod["actions"].insert(0, new_action)

if in_vlan:
if in_vlan is not None:
# if in_vlan is set, it must be included in the match
flow_mod["match"]["dl_vlan"] = in_vlan
if new_c_vlan:
# new_in_vlan is set, so an action to set it is necessary

if new_c_vlan not in {"4096/4096", 0, None}:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
# new_in_vlan is an integer but zero, action to set is required
new_action = {"action_type": "set_vlan", "vlan_id": new_c_vlan}
flow_mod["actions"].insert(0, new_action)
if not in_vlan:
# new_in_vlan is set, but in_vlan is not, so there was no
# vlan set; then it is set now
new_action = {"action_type": "push_vlan", "tag_type": "c"}
flow_mod["actions"].insert(0, new_action)
elif in_vlan:
# in_vlan is set, but new_in_vlan is not, so the existing vlan
# must be removed

if in_vlan not in {"4096/4096", 0, None} and new_c_vlan == 0:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
# # new_in_vlan is an integer but zero and new_c_vlan does not
# a pop action is required
new_action = {"action_type": "pop_vlan"}
flow_mod["actions"].insert(0, new_action)

elif in_vlan == "4096/4096" and new_c_vlan == 0:
# if in_vlan match with any tags and new_c_vlan does not
# a pop action is required
new_action = {"action_type": "pop_vlan"}
flow_mod["actions"].insert(0, new_action)

elif not in_vlan and new_c_vlan not in {"4096/4096", 0, None}:
viniarck marked this conversation as resolved.
Show resolved Hide resolved
# new_in_vlan is an integer but zero and in_vlan is not set
# then it is set now
new_action = {"action_type": "push_vlan", "tag_type": "c"}
flow_mod["actions"].insert(0, new_action)

return flow_mod

def _prepare_pop_flow(
Expand Down
12 changes: 7 additions & 5 deletions openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -598,16 +598,18 @@ components:

Tag: # Can be referenced via '#/components/schemas/Tag'
type: object
required:
- tag_type
- value
properties:
tag_type:
type: integer
format: int32
value:
type: integer
format: int32
oneOf:
- type: integer
format: int32
- type: string
viniarck marked this conversation as resolved.
Show resolved Hide resolved
enum:
- any
- untagged

CircuitSchedule: # Can be referenced via '#/components/schemas/CircuitSchedule'
type: object
Expand Down
2 changes: 2 additions & 0 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
# is not set in a request
EVPL_SB_PRIORITY = 20000
EPL_SB_PRIORITY = 10000
ANY_SB_PRIORITY = 15000
UNTAGGED_SB_PRIORITY = 20000

# Time (seconds) to check if an evc has been updated
# or flows have been deleted.
Expand Down
Loading