Merge "NSX|V3: Add DHCP relay firewall rules"
This commit is contained in:
commit
57b1ca6d64
@ -181,7 +181,7 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
|
||||
nsxlib.feature_supported(nsxlib_consts.FEATURE_DHCP_RELAY)):
|
||||
relay_id = None
|
||||
if cfg.CONF.nsx_v3.init_objects_by_tags:
|
||||
# Find the TZ by its tag
|
||||
# Find the relay service by its tag
|
||||
relay_id = nsxlib.get_id_by_resource_and_tag(
|
||||
nsxlib.relay_service.resource_type,
|
||||
cfg.CONF.nsx_v3.search_objects_scope,
|
||||
@ -191,8 +191,13 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
|
||||
relay_id = nsxlib.relay_service.get_id_by_name_or_id(
|
||||
self.dhcp_relay_service)
|
||||
self.dhcp_relay_service = relay_id
|
||||
# if there is a relay service - also find the server ips
|
||||
if self.dhcp_relay_service:
|
||||
self.dhcp_relay_servers = nsxlib.relay_service.get_server_ips(
|
||||
self.dhcp_relay_service)
|
||||
else:
|
||||
self.dhcp_relay_service = None
|
||||
self.dhcp_relay_servers = None
|
||||
|
||||
|
||||
class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones):
|
||||
|
@ -3305,6 +3305,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
return self.fwaas_callbacks.update_router_firewall(
|
||||
context, self.nsxlib, router_id, ports)
|
||||
|
||||
def _get_port_relay_servers(self, context, port_id, network_id=None):
|
||||
if not network_id:
|
||||
port = self.get_port(context, port_id)
|
||||
network_id = port['network_id']
|
||||
net_az = self.get_network_az_by_net_id(context, network_id)
|
||||
return net_az.dhcp_relay_servers
|
||||
|
||||
def _get_port_relay_services(self):
|
||||
# DHCP services: UDP 67, 68, 2535
|
||||
#TODO(asarfaty): use configurable ports
|
||||
service1 = self.nsxlib.firewall_section.get_nsservice(
|
||||
nsxlib_consts.L4_PORT_SET_NSSERVICE,
|
||||
l4_protocol=nsxlib_consts.UDP,
|
||||
destination_ports=['67-68'])
|
||||
service2 = self.nsxlib.firewall_section.get_nsservice(
|
||||
nsxlib_consts.L4_PORT_SET_NSSERVICE,
|
||||
l4_protocol=nsxlib_consts.UDP,
|
||||
destination_ports=['2535'])
|
||||
return [service1, service2]
|
||||
|
||||
def get_extra_fw_rules(self, context, router_id, port_id=None):
|
||||
"""Return firewall rules that should be added to the router firewall
|
||||
|
||||
@ -3317,8 +3337,59 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
port should be returned, and the rules should be ingress/egress
|
||||
(but not both) and include the source/dest nsx logical port.
|
||||
"""
|
||||
#TODO(asarfaty): DHCP relay rules
|
||||
return []
|
||||
extra_rules = []
|
||||
# DHCP relay rules:
|
||||
# get the list of relevant relay servers
|
||||
elv_ctx = context.elevated()
|
||||
if port_id:
|
||||
relay_servers = self._get_port_relay_servers(elv_ctx, port_id)
|
||||
else:
|
||||
relay_servers = []
|
||||
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
|
||||
'device_id': [router_id]}
|
||||
ports = self.get_ports(elv_ctx, filters=filters)
|
||||
for port in ports:
|
||||
port_relay_servers = self._get_port_relay_servers(
|
||||
elv_ctx, port['id'], network_id=port['network_id'])
|
||||
if port_relay_servers:
|
||||
relay_servers.extend(port_relay_servers)
|
||||
|
||||
# Add rules to allow dhcp traffic relay servers
|
||||
if relay_servers:
|
||||
# if it is a single port, the source/dest is this logical port
|
||||
if port_id:
|
||||
_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
|
||||
context.session, port_id)
|
||||
port_target = [{'target_type': 'LogicalPort',
|
||||
'target_id': nsx_port_id}]
|
||||
else:
|
||||
port_target = None
|
||||
# translate the relay server ips to the firewall format
|
||||
relay_target = []
|
||||
if self.fwaas_callbacks:
|
||||
relay_target = (self.fwaas_callbacks.fwaas_driver.
|
||||
translate_addresses_to_target(set(relay_servers)))
|
||||
|
||||
dhcp_services = self._get_port_relay_services()
|
||||
|
||||
# ingress rule
|
||||
extra_rules.append({
|
||||
'display_name': "DHCP Relay ingress traffic",
|
||||
'action': nsxlib_consts.FW_ACTION_ALLOW,
|
||||
'sources': relay_target,
|
||||
'destinations': port_target,
|
||||
'services': dhcp_services,
|
||||
'direction': 'IN'})
|
||||
# egress rule
|
||||
extra_rules.append({
|
||||
'display_name': "DHCP Relay egress traffic",
|
||||
'action': nsxlib_consts.FW_ACTION_ALLOW,
|
||||
'destinations': relay_target,
|
||||
'sources': port_target,
|
||||
'services': dhcp_services,
|
||||
'direction': 'OUT'})
|
||||
|
||||
return extra_rules
|
||||
|
||||
def _get_ports_and_address_groups(self, context, router_id, network_id,
|
||||
exclude_sub_ids=None):
|
||||
|
@ -98,7 +98,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
|
||||
cidr,
|
||||
consts.IPV6 if netaddr.valid_ipv6(cidr) else consts.IPV4)
|
||||
|
||||
def _translate_addresses(self, cidrs):
|
||||
def translate_addresses_to_target(self, cidrs):
|
||||
return [self._translate_cidr(ip) for ip in cidrs]
|
||||
|
||||
@staticmethod
|
||||
@ -170,7 +170,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
|
||||
'target_id': replace_dest}]
|
||||
nsx_rule['direction'] = 'IN'
|
||||
elif rule.get('destination_ip_address'):
|
||||
nsx_rule['destinations'] = self._translate_addresses(
|
||||
nsx_rule['destinations'] = self.translate_addresses_to_target(
|
||||
[rule['destination_ip_address']])
|
||||
if replace_src:
|
||||
# set this value as the source logical port,
|
||||
@ -179,7 +179,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
|
||||
'target_id': replace_src}]
|
||||
nsx_rule['direction'] = 'OUT'
|
||||
elif rule.get('source_ip_address'):
|
||||
nsx_rule['sources'] = self._translate_addresses(
|
||||
nsx_rule['sources'] = self.translate_addresses_to_target(
|
||||
[rule['source_ip_address']])
|
||||
if rule.get('protocol'):
|
||||
nsx_rule['services'] = self._translate_services(rule)
|
||||
|
@ -53,7 +53,6 @@ class Nsxv3FwaasCallbacksV1(com_clbcks.NsxFwaasCallbacks):
|
||||
This method should be called on FWaaS updates, and on router
|
||||
interfaces changes.
|
||||
"""
|
||||
|
||||
# find the backend router and its firewall section
|
||||
nsx_id, sect_id = self.fwaas_driver.get_backend_router_and_fw_section(
|
||||
context, router_id)
|
||||
|
@ -188,27 +188,37 @@ def update_dhcp_relay(resource, event, trigger, **kwargs):
|
||||
# initialize the availability zones and nsxlib
|
||||
config.register_nsxv3_azs(cfg.CONF, cfg.CONF.nsx_v3.availability_zones)
|
||||
|
||||
# get all neutron router interfaces ports
|
||||
admin_cxt = neutron_context.get_admin_context()
|
||||
with utils.NsxV3PluginWrapper() as plugin:
|
||||
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
|
||||
ports = plugin.get_ports(admin_cxt, filters=filters)
|
||||
for port in ports:
|
||||
# get the backend router port by the tag
|
||||
nsx_port_id = nsxlib.get_id_by_resource_and_tag(
|
||||
'LogicalRouterDownLinkPort',
|
||||
'os-neutron-rport-id', port['id'])
|
||||
if not nsx_port_id:
|
||||
LOG.warning("Couldn't find nsx router port for interface %s",
|
||||
port['id'])
|
||||
continue
|
||||
# get the network of this port
|
||||
network_id = port['network_id']
|
||||
# check the relay service on the az of the network
|
||||
az = plugin.get_network_az_by_net_id(admin_cxt, network_id)
|
||||
nsxlib.logical_router_port.update(
|
||||
nsx_port_id, relay_service_uuid=az.dhcp_relay_service)
|
||||
#TODO(asarfaty) also update the firewall rules of the routers
|
||||
# Make sure FWaaS was initialized
|
||||
plugin.init_fwaas_for_admin_utils()
|
||||
|
||||
# get all neutron routers and interfaces ports
|
||||
routers = plugin.get_routers(admin_cxt)
|
||||
for router in routers:
|
||||
LOG.info("Updating router %s", router['id'])
|
||||
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
|
||||
'device_id': [router['id']]}
|
||||
ports = plugin.get_ports(admin_cxt, filters=filters)
|
||||
for port in ports:
|
||||
# get the backend router port by the tag
|
||||
nsx_port_id = nsxlib.get_id_by_resource_and_tag(
|
||||
'LogicalRouterDownLinkPort',
|
||||
'os-neutron-rport-id', port['id'])
|
||||
if not nsx_port_id:
|
||||
LOG.warning("Couldn't find nsx router port for interface "
|
||||
"%s", port['id'])
|
||||
continue
|
||||
# get the network of this port
|
||||
network_id = port['network_id']
|
||||
# check the relay service on the az of the network
|
||||
az = plugin.get_network_az_by_net_id(admin_cxt, network_id)
|
||||
nsxlib.logical_router_port.update(
|
||||
nsx_port_id, relay_service_uuid=az.dhcp_relay_service)
|
||||
|
||||
# if FWaaS is enables, also update the firewall rules
|
||||
plugin.update_router_firewall(admin_cxt, router['id'])
|
||||
|
||||
LOG.info("Done.")
|
||||
|
||||
|
||||
|
@ -13,14 +13,22 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from neutron.db import db_base_plugin_v2
|
||||
from neutron import manager
|
||||
from neutron_lib import context
|
||||
from neutron_lib.plugins import constants as const
|
||||
from neutron_lib.plugins import directory
|
||||
|
||||
from neutron_fwaas.services.firewall import fwaas_plugin as fwaas_plugin_v1
|
||||
from neutron_fwaas.services.firewall import fwaas_plugin_v2
|
||||
|
||||
from vmware_nsx.db import db as nsx_db
|
||||
from vmware_nsx.plugins.nsx_v3 import plugin
|
||||
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
|
||||
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v1
|
||||
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v2
|
||||
from vmware_nsxlib.v3 import nsx_constants
|
||||
|
||||
_NSXLIB = None
|
||||
@ -107,6 +115,35 @@ class NsxV3PluginWrapper(plugin.NsxV3Plugin):
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
directory.add_plugin(const.CORE, None)
|
||||
|
||||
def _init_fwaas_plugin(self, provider, callbacks_class, plugin_callbacks):
|
||||
fwaas_plugin_class = manager.NeutronManager.load_class_for_provider(
|
||||
'neutron.service_plugins', provider)
|
||||
fwaas_plugin = fwaas_plugin_class()
|
||||
self.fwaas_callbacks = callbacks_class(self.nsxlib)
|
||||
# override the fwplugin_rpc since there is no RPC support in adminutils
|
||||
self.fwaas_callbacks.fwplugin_rpc = plugin_callbacks(fwaas_plugin)
|
||||
|
||||
def init_fwaas_for_admin_utils(self):
|
||||
# initialize the FWaaS plugin and callbacks
|
||||
self.fwaas_callbacks = None
|
||||
# This is an ugly patch to find out if it is v1 or v2
|
||||
service_plugins = cfg.CONF.service_plugins
|
||||
for srv_plugin in service_plugins:
|
||||
if 'firewall' in srv_plugin:
|
||||
if 'v2' in srv_plugin:
|
||||
# FWaaS V2
|
||||
self._init_fwaas_plugin(
|
||||
'firewall_v2',
|
||||
fwaas_callbacks_v2.Nsxv3FwaasCallbacksV2,
|
||||
fwaas_plugin_v2.FirewallCallbacks)
|
||||
else:
|
||||
# FWaaS V1
|
||||
self._init_fwaas_plugin(
|
||||
'firewall',
|
||||
fwaas_callbacks_v1.Nsxv3FwaasCallbacksV1,
|
||||
fwaas_plugin_v1.FirewallCallbacks)
|
||||
return
|
||||
|
||||
def _init_dhcp_metadata(self):
|
||||
pass
|
||||
|
||||
|
@ -29,6 +29,9 @@ from vmware_nsxlib.v3 import nsx_constants as consts
|
||||
FAKE_FW_ID = 'fake_fw_uuid'
|
||||
FAKE_ROUTER_ID = 'fake_rtr_uuid'
|
||||
MOCK_NSX_ID = 'nsx_router_id'
|
||||
FAKE_PORT_ID = 'fake_port_uuid'
|
||||
FAKE_NET_ID = 'fake_net_uuid'
|
||||
FAKE_NSX_PORT_ID = 'fake_nsx_port_uuid'
|
||||
MOCK_DEFAULT_RULE_ID = 'nsx_default_rule_id'
|
||||
MOCK_SECTION_ID = 'sec_id'
|
||||
DEFAULT_RULE = {'is_default': True,
|
||||
@ -177,6 +180,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
"update") as update_fw, \
|
||||
mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_ports',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_router',
|
||||
return_value=apply_list[0]), \
|
||||
mock.patch.object(self.plugin.fwaas_callbacks,
|
||||
@ -199,6 +204,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
"update") as update_fw,\
|
||||
mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_ports',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_router',
|
||||
return_value=apply_list[0]), \
|
||||
mock.patch.object(self.plugin.fwaas_callbacks,
|
||||
@ -269,6 +276,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
"update") as update_fw, \
|
||||
mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_ports',
|
||||
return_value=[]), \
|
||||
mock.patch.object(self.plugin, 'get_router',
|
||||
return_value=apply_list[0]), \
|
||||
mock.patch.object(self.plugin.fwaas_callbacks,
|
||||
@ -281,3 +290,47 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
update_fw.assert_called_once_with(
|
||||
MOCK_SECTION_ID,
|
||||
rules=[self._default_rule()])
|
||||
|
||||
def test_create_firewall_with_dhcp_relay(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
firewall = self._fake_firewall_no_rule()
|
||||
relay_server = '1.1.1.1'
|
||||
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
|
||||
with mock.patch("vmware_nsxlib.v3.security.NsxLibFirewallSection."
|
||||
"update") as update_fw,\
|
||||
mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[port]), \
|
||||
mock.patch.object(self.plugin, 'get_ports',
|
||||
return_value=[port]), \
|
||||
mock.patch.object(self.plugin, 'get_router',
|
||||
return_value=apply_list[0]), \
|
||||
mock.patch.object(self.plugin, '_get_port_relay_servers',
|
||||
return_value=[relay_server]),\
|
||||
mock.patch.object(self.plugin.fwaas_callbacks,
|
||||
'_get_router_firewall_id',
|
||||
return_value=firewall['id']), \
|
||||
mock.patch.object(self.plugin.fwaas_callbacks,
|
||||
'_get_fw_from_plugin',
|
||||
return_value=firewall):
|
||||
self.firewall.create_firewall('nsx', apply_list, firewall)
|
||||
# expecting 2 allow rules for the relay servers + default rule
|
||||
expected_rules = expected_rules = [
|
||||
{'display_name': "DHCP Relay ingress traffic",
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'destinations': None,
|
||||
'sources': [{'target_id': relay_server,
|
||||
'target_type': 'IPv4Address'}],
|
||||
'services': self.plugin._get_port_relay_services(),
|
||||
'direction': 'IN'},
|
||||
{'display_name': "DHCP Relay egress traffic",
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'sources': None,
|
||||
'destinations': [{'target_id': relay_server,
|
||||
'target_type': 'IPv4Address'}],
|
||||
'services': self.plugin._get_port_relay_services(),
|
||||
'direction': 'OUT'},
|
||||
self._default_rule()
|
||||
]
|
||||
update_fw.assert_called_once_with(
|
||||
MOCK_SECTION_ID,
|
||||
rules=expected_rules)
|
||||
|
@ -29,6 +29,7 @@ from vmware_nsxlib.v3 import nsx_constants as consts
|
||||
FAKE_FW_ID = 'fake_fw_uuid'
|
||||
FAKE_ROUTER_ID = 'fake_rtr_uuid'
|
||||
FAKE_PORT_ID = 'fake_port_uuid'
|
||||
FAKE_NET_ID = 'fake_net_uuid'
|
||||
FAKE_NSX_PORT_ID = 'fake_nsx_port_uuid'
|
||||
MOCK_NSX_ID = 'nsx_nsx_router_id'
|
||||
MOCK_DEFAULT_RULE_ID = 'nsx_default_rule_id'
|
||||
@ -191,9 +192,11 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
def test_create_firewall_no_rules(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
firewall = self._fake_empty_firewall_group()
|
||||
port = {'id': FAKE_PORT_ID}
|
||||
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
|
||||
with mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[port]),\
|
||||
mock.patch.object(self.plugin, 'get_port',
|
||||
return_value=port),\
|
||||
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
|
||||
return_value=firewall),\
|
||||
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
|
||||
@ -224,9 +227,11 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
apply_list = self._fake_apply_list()
|
||||
rule_list = self._fake_rules_v4(is_ingress=is_ingress)
|
||||
firewall = self._fake_firewall_group(rule_list, is_ingress=is_ingress)
|
||||
port = {'id': FAKE_PORT_ID}
|
||||
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
|
||||
with mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[port]),\
|
||||
mock.patch.object(self.plugin, 'get_port',
|
||||
return_value=port),\
|
||||
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
|
||||
return_value=firewall),\
|
||||
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
|
||||
@ -302,3 +307,57 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
|
||||
update_fw.assert_called_once_with(
|
||||
MOCK_SECTION_ID,
|
||||
rules=[self._default_rule()])
|
||||
|
||||
def test_create_firewall_with_dhcp_relay(self):
|
||||
apply_list = self._fake_apply_list()
|
||||
firewall = self._fake_empty_firewall_group()
|
||||
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
|
||||
relay_server = '1.1.1.1'
|
||||
with mock.patch.object(self.plugin, '_get_router_interfaces',
|
||||
return_value=[port]),\
|
||||
mock.patch.object(self.plugin, 'get_port',
|
||||
return_value=port),\
|
||||
mock.patch.object(self.plugin, '_get_port_relay_servers',
|
||||
return_value=[relay_server]),\
|
||||
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
|
||||
return_value=firewall),\
|
||||
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
|
||||
return_value=(0, FAKE_NSX_PORT_ID)),\
|
||||
mock.patch("vmware_nsxlib.v3.security.NsxLibFirewallSection."
|
||||
"update") as update_fw:
|
||||
self.firewall.create_firewall_group('nsx', apply_list, firewall)
|
||||
# expecting 2 allow rules for the relay servers,
|
||||
# 2 block rules for the logical port (egress & ingress)
|
||||
# and last default allow all rule
|
||||
expected_rules = [
|
||||
{'display_name': "DHCP Relay ingress traffic",
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'destinations': [{'target_type': 'LogicalPort',
|
||||
'target_id': FAKE_NSX_PORT_ID}],
|
||||
'sources': [{'target_id': relay_server,
|
||||
'target_type': 'IPv4Address'}],
|
||||
'services': self.plugin._get_port_relay_services(),
|
||||
'direction': 'IN'},
|
||||
{'display_name': "DHCP Relay egress traffic",
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'sources': [{'target_type': 'LogicalPort',
|
||||
'target_id': FAKE_NSX_PORT_ID}],
|
||||
'destinations': [{'target_id': relay_server,
|
||||
'target_type': 'IPv4Address'}],
|
||||
'services': self.plugin._get_port_relay_services(),
|
||||
'direction': 'OUT'},
|
||||
{'display_name': "Block port ingress",
|
||||
'action': consts.FW_ACTION_DROP,
|
||||
'destinations': [{'target_type': 'LogicalPort',
|
||||
'target_id': FAKE_NSX_PORT_ID}],
|
||||
'direction': 'IN'},
|
||||
{'display_name': "Block port egress",
|
||||
'action': consts.FW_ACTION_DROP,
|
||||
'sources': [{'target_type': 'LogicalPort',
|
||||
'target_id': FAKE_NSX_PORT_ID}],
|
||||
'direction': 'OUT'},
|
||||
self._default_rule()
|
||||
]
|
||||
update_fw.assert_called_once_with(
|
||||
MOCK_SECTION_ID,
|
||||
rules=expected_rules)
|
||||
|
Loading…
Reference in New Issue
Block a user