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

Segment Ports QoS Profile Mappings support #135

Open
wants to merge 2 commits into
base: stable/yoga-m3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
6 changes: 3 additions & 3 deletions networking_nsxv3/api/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,18 @@ def get_network_bridge(self, current, network_segments, network_current, host):
)

def create_policy(self, context, policy):
LOG.debug("All gents. Creating policy={}.".format(policy.name))
LOG.debug("All gents. Creating policy={}.".format(policy["id"]))
return self._get_call_context().cast(
self.context, 'create_policy', policy=policy)

def update_policy(self, context, policy):
LOG.debug("All gents. Updating policy={}.".format(policy.name))
LOG.debug("All gents. Updating policy={}.".format(policy["id"]))
if (hasattr(policy, "rules")):
return self._get_call_context().cast(
self.context, 'update_policy', policy=policy)

def delete_policy(self, context, policy):
LOG.debug("All gents. Deleting policy={}.".format(policy.name))
LOG.debug("All gents. Deleting policy={}.".format(policy["id"]))
return self._get_call_context().cast(
self.context, 'delete_policy', policy=policy)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,12 @@ class API(object):
SEARCH_Q_SEG_PORTS = {"query": "resource_type:SegmentPort AND marked_for_delete:false"}
SEARCH_Q_QOS_PROFILES = {
"query": "resource_type:QoSProfile AND NOT display_name:*default* AND marked_for_delete:false"}
SEARCH_Q_ALL_SEG_PROFILES = {
"query":
"resource_type:QoSProfile" +
" OR resource_type:SpoofGuardProfile" +
" OR resource_type:SegmentSecurityProfile" +
" OR resource_type:PortMirroringProfile" +
" OR resource_type:MacDiscoveryProfile" +
" OR resource_type:IPDiscoveryProfile"
}
SEARCH_Q_QOS_BIND = "resource_type:PortQoSProfileBindingMap AND qos_profile_path:\"/infra/qos-profiles/{}\""
SEARCH_Q_QOS_BIND_BY_PPATH = "resource_type:PortQoSProfileBindingMap AND parent_path:\"{}\""

SEARCH_DSL = POLICY_BASE + "/search"
SEARCH_DSL_QUERY = lambda res_type, dsl: {

def SEARCH_DSL_QUERY(res_type, dsl): return {
"query": f"resource_type:{res_type}",
"dsl": f"{dsl}",
"data_source": "INTENT",
Expand All @@ -96,6 +90,7 @@ class API(object):
SEGMENT_PORTS = INFRA + "/segments/{}/ports"
SEGMENT_PORT_PATH = "/infra/segments/{}/ports/{}"
SEGMENT_PORT = POLICY_BASE + SEGMENT_PORT_PATH
SEGMENT_PORT_QOS = SEGMENT_PORT + "/port-qos-profile-binding-maps/{}"

GROUP_PATH = "/infra/domains/default/groups/{}"
GROUPS = INFRA + "/domains/default/groups"
Expand Down Expand Up @@ -355,9 +350,15 @@ def segment_port(self, os_port, provider_port) -> dict:

return segment_port

def qos_profile_binding(self) -> dict:
# TODO: QOS Profile Binding
return {}
def qos_profile_binding(self, qos_id: str, nsx_seg_id: str, nsx_port_id: str) -> dict:
return {
"qos_profile_path": f"/infra/qos-profiles/{qos_id}",
"resource_type": "PortQoSProfileBindingMap",
"id": qos_id,
"display_name": qos_id,
"path": f"/infra/segments/{nsx_seg_id}/ports/{nsx_port_id}/port-qos-profile-binding-maps/{qos_id}",
"parent_path": f"/infra/segments/{nsx_seg_id}/ports/{nsx_port_id}"
}

# NSX-T Group Members
def sg_members_container(self, os_sg: dict, provider_sg: dict) -> dict:
Expand Down Expand Up @@ -884,7 +885,8 @@ def address_group_realize(self, os_ag, delete=False):

def _clear_all_static_memberships_for_port(self, port_meta: PolicyResourceMeta):
# Get all SGs where the port might have been a static member
grps:List[dict] = self.client.get_all(path=API.SEARCH_DSL, params=API.SEARCH_DSL_QUERY("Group", port_meta.real_id))
grps: List[dict] = self.client.get_all(
path=API.SEARCH_DSL, params=API.SEARCH_DSL_QUERY("Group", port_meta.real_id))
if len(grps) > 0:
# Remove the port path from the SGs PathExpressions
LOG.info("Removing static member's port.path '%s' from %s SGs", port_meta.path, len(grps))
Expand Down Expand Up @@ -966,7 +968,40 @@ def port_realize(self, os_port: dict, delete=False):
LOG.info("Port: %s has %s security groups which is more than the maximum allowed %s. \
The port will be added to the security groups as a static member.", port_id, len(port_sgs), max_sg_tags)
os_port["security_groups"] = None
return self._realize(Provider.PORT, False, self.payload.segment_port, os_port, provider_port)

updated_port_meta = self._realize(Provider.PORT, False, self.payload.segment_port, os_port, provider_port)
self.realize_qos_profile_binding(segment_meta=segment_meta, port_meta=updated_port_meta, os_port=os_port)

return updated_port_meta

def realize_qos_profile_binding(self, segment_meta: PolicyResourceMeta, port_meta: PolicyResourceMeta, os_port: dict):
if not segment_meta or not port_meta:
LOG.debug("QoS Profile Binding Segment: '%s', Port: '%s'", segment_meta, port_meta)
LOG.info("Skipping QoS Profile Binding for Port:%s", os_port.get("id"))
return

os_qos_id = os_port.get("qos_policy_id")

try:
if os_qos_id:
qos_meta = self.metadata(Provider.QOS, os_qos_id)
if not qos_meta:
LOG.warning("Not found. QoS:%s for Port:%s. QoS Profile Binding skipped.",
os_qos_id, os_port.get("id"))
return
qos_bind = self.payload.qos_profile_binding(qos_meta.real_id, segment_meta.real_id, port_meta.real_id)
api_path = API.SEGMENT_PORT_QOS.format(segment_meta.real_id, port_meta.real_id, qos_meta.real_id)
self.client.patch(api_path, data=qos_bind)
else:
qos_maps = self.client.get_all(
API.SEARCH_QUERY, {"query": API.SEARCH_Q_QOS_BIND_BY_PPATH.format(port_meta.path)})
for qm in qos_maps:
self.client.delete(API.POLICY_BASE + qm["path"])
except Exception as e:
b = "bind" if os_qos_id else "unbind"
LOG.warning(f"Unable to {b} a QOS: '{os_qos_id}' for Port: '{os_port.get('id')}'")
LOG.debug(e)

def get_port(self, os_id):
port = self.client.get_unique(path=API.SEARCH_QUERY, params={"query": API.SEARCH_Q_SEG_PORT.format(os_id)})
if port:
Expand All @@ -993,11 +1028,6 @@ def network_realize(self, segmentation_id: int) -> PolicyResourceMeta:
segment = self._realize(Provider.NETWORK, False, self.payload.segment, os_net, provider_net)
return segment

def get_non_default_switching_profiles(self) -> list:
prfls = self.client.get_all(path=API.SEARCH_QUERY, params=API.SEARCH_Q_ALL_SEG_PROFILES)
# filter the list
return [p for p in prfls if p and p.get("id").find("default") == -1]

# overrides
def sg_rules_realize(self, os_sg, delete=False, logged=False):
os_id = os_sg.get("id")
Expand All @@ -1016,6 +1046,11 @@ def qos_realize(self, qos: dict, delete=False):
meta = self.metadata(Provider.QOS, qos_id)
provider_o = {"id": qos_id, "_revision": None} if not meta else {
"id": meta.real_id, "_revision": meta.revision}
if delete and meta:
qos_maps: list[Dict] = self.client.get_all(
API.SEARCH_QUERY, {"query": API.SEARCH_Q_QOS_BIND.format(meta.real_id)})
for qm in qos_maps:
self.client.delete(API.POLICY_BASE + qm["path"])
return self._realize(Provider.QOS, delete, self.payload.qos, qos, provider_o)

def sg_members_realize(self, os_sg: dict, delete=False):
Expand Down
2 changes: 1 addition & 1 deletion networking_nsxv3/tests/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,4 @@
## Address Groups
- Create/Delete/Update IPv4/IPv6 address groups [implemented]
- Create mixed IPv4/IPv6 members [implemented]
- Address group in multiple security groups [implemented]
- Address group in multiple security groups [implemented]
59 changes: 49 additions & 10 deletions networking_nsxv3/tests/functional/test_realization_brownfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from neutron.tests import base
from networking_nsxv3.tests.environment import Environment
from networking_nsxv3.tests.datasets import coverage
from networking_nsxv3.plugins.ml2.drivers.nsxv3.agent import provider_nsx_policy
from networking_nsxv3.plugins.ml2.drivers.nsxv3.agent import provider_nsx_policy as pp
import copy
import os
import re
Expand Down Expand Up @@ -225,7 +225,7 @@ def end_to_end_test_generator():
yield 11

@staticmethod
def _assert_create(os_inventory, environment):
def _assert_create(os_inventory: coverage, environment: Environment):
c = os_inventory
mgmt_meta, plcy_meta = environment.dump_provider_inventory(printable=False)
m = {**mgmt_meta, **plcy_meta}
Expand All @@ -240,6 +240,45 @@ def _assert_create(os_inventory, environment):
TestAgentRealizer.instance.assertEquals(c.QOS_INTERNAL["id"] in m[p.QOS]["meta"], True)
TestAgentRealizer.instance.assertEquals(c.QOS_EXTERNAL["id"] in m[p.QOS]["meta"], True)
TestAgentRealizer.instance.assertEquals(c.QOS_NOT_REFERENCED["id"] in m[p.QOS]["meta"], False)

# Validate QoS Bindings
internal_qos_id = m[p.QOS]["meta"][c.QOS_INTERNAL["id"]]["id"]
internal_qos_meta = m[p.QOS]["meta"][c.QOS_INTERNAL["id"]]
internal_port_meta = m[p.PORT]["meta"][c.PORT_FRONTEND_INTERNAL["id"]]

external_qos_id = m[p.QOS]["meta"][c.QOS_EXTERNAL["id"]]["id"]
external_qos_meta = m[p.QOS]["meta"][c.QOS_EXTERNAL["id"]]
external_port_meta = m[p.PORT]["meta"][c.PORT_FRONTEND_EXTERNAL["id"]]

internal_qos_query = pp.API.SEARCH_Q_QOS_BIND.format(internal_qos_id)
internal_qos_mappings = p.client.get_all(path=pp.API.SEARCH_QUERY, params={"query": internal_qos_query})

external_qos_query = pp.API.SEARCH_Q_QOS_BIND.format(external_qos_id)
external_qos_mappings = p.client.get_all(path=pp.API.SEARCH_QUERY, params={"query": external_qos_query})

internal_qos_data = {
"display_name": internal_qos_meta["real_id"],
"id": internal_qos_meta["real_id"],
"marked_for_delete": False,
"parent_path": internal_port_meta["path"],
"path": internal_port_meta["path"] + f"/port-qos-profile-binding-maps/{internal_qos_id}",
"qos_profile_path": internal_qos_meta["path"],
"resource_type": "PortQoSProfileBindingMap"
}
external_qos_data = {
"display_name": external_qos_meta["real_id"],
"id": external_qos_meta["real_id"],
"marked_for_delete": False,
"parent_path": external_port_meta["path"],
"path": external_port_meta["path"] + f"/port-qos-profile-binding-maps/{external_qos_id}",
"qos_profile_path": external_qos_meta["path"],
"resource_type": "PortQoSProfileBindingMap"
}

TestAgentRealizer.instance.assertEqual(1, len(external_qos_mappings))
TestAgentRealizer.instance.assertEqual(1, len(internal_qos_mappings))
TestAgentRealizer.instance.assertDictSupersetOf(external_qos_data, external_qos_mappings[0])
TestAgentRealizer.instance.assertDictSupersetOf(internal_qos_data, internal_qos_mappings[0])

# Validate Security Groups Members
TestAgentRealizer.instance.assertEquals(c.SECURITY_GROUP_FRONTEND["id"] in m[p.SG_MEMBERS]["meta"], True)
Expand Down Expand Up @@ -275,7 +314,7 @@ def _assert_create(os_inventory, environment):
TestAgentRealizer.instance.assertEquals("0.0.0.0/" in id or "::/" in id, True)

@staticmethod
def _assert_update(os_inventory, environment):
def _assert_update(os_inventory: coverage, environment: Environment):
c = os_inventory
mgmt_meta, plcy_meta = environment.dump_provider_inventory(printable=False)
m = {**mgmt_meta, **plcy_meta}
Expand Down Expand Up @@ -329,7 +368,7 @@ def _assert_update(os_inventory, environment):
TestAgentRealizer.instance.assertEquals("0.0.0.0/" in id or "::/" in id, True)

params = {"default_service": False} # User services only
services = p.client.get_all(path=provider_nsx_policy.API.SERVICES, params=params)
services = p.client.get_all(path=pp.API.SERVICES, params=params)
services = [s for s in services if not s.get("is_default")]
TestAgentRealizer.instance.assertEquals(len(services), 0)

Expand All @@ -344,14 +383,14 @@ def _pollute(env, index):
ipv4_id = re.sub(r"\.|:|\/", "-", ipv4)
ipv6_id = re.sub(r"\.|:|\/", "-", ipv6)

pp = provider_nsx_policy.Payload()
api = provider_nsx_policy.API
ppp = pp.Payload()
api = pp.API

p.client.put(path=api.GROUP.format(ipv4_id), data=pp.sg_rule_remote(ipv4))
p.client.put(path=api.GROUP.format(ipv6_id), data=pp.sg_rule_remote(ipv6))
p.client.put(path=api.GROUP.format(ipv4_id), data=ppp.sg_rule_remote(ipv4))
p.client.put(path=api.GROUP.format(ipv6_id), data=ppp.sg_rule_remote(ipv6))

p.client.put(path=api.GROUP.format(_id), data=pp.sg_members_container({"id": _id}, dict()))
data = pp.sg_rules_container({"id": _id}, {"rules": [], "scope": _id})
p.client.put(path=api.GROUP.format(_id), data=ppp.sg_members_container({"id": _id}, dict()))
data = ppp.sg_rules_container({"id": _id}, {"rules": [], "scope": _id})
p.client.put(path=api.POLICY.format(_id), data=data)


Expand Down
Loading