Skip to content

Commit

Permalink
Segment Ports QoS Profile Mappings support
Browse files Browse the repository at this point in the history
  • Loading branch information
sven-rosenzweig committed Feb 27, 2024
1 parent b0c732f commit e01072a
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 33 deletions.
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
15 changes: 15 additions & 0 deletions networking_nsxv3/tests/e2e/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# End-to-End test cases
## Address Groups
* Create IPv4 address groups
* Create IPv6 address groups
* Create mixed IPv4/IPv6 address groups
* Update address groups
* Membership of address group in multiple Security Groups
* Delete address groups

## Security Groups
* TODO
## Ports
* TODO
## QoS
* TODO
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

0 comments on commit e01072a

Please sign in to comment.