Merge "[ovn] Use normalized remote prefix IPs in OVN driver"
This commit is contained in:
commit
000e54d0da
@ -12,14 +12,19 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import netaddr
|
||||||
from neutron_lib import constants as const
|
from neutron_lib import constants as const
|
||||||
from neutron_lib import exceptions as n_exceptions
|
from neutron_lib import exceptions as n_exceptions
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
from neutron.common.ovn import constants as ovn_const
|
from neutron.common.ovn import constants as ovn_const
|
||||||
from neutron.common.ovn import utils
|
from neutron.common.ovn import utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Convert the protocol number from integer to strings because that's
|
# Convert the protocol number from integer to strings because that's
|
||||||
# how Neutron will pass it to us
|
# how Neutron will pass it to us
|
||||||
PROTOCOL_NAME_TO_NUM_MAP = {k: str(v) for k, v in
|
PROTOCOL_NAME_TO_NUM_MAP = {k: str(v) for k, v in
|
||||||
@ -84,9 +89,17 @@ def acl_ethertype(r):
|
|||||||
def acl_remote_ip_prefix(r, ip_version):
|
def acl_remote_ip_prefix(r, ip_version):
|
||||||
if not r['remote_ip_prefix']:
|
if not r['remote_ip_prefix']:
|
||||||
return ''
|
return ''
|
||||||
|
cidr = netaddr.IPNetwork(r['remote_ip_prefix'])
|
||||||
|
normalized_ip_prefix = "%s/%s" % (cidr.network, cidr.prefixlen)
|
||||||
|
if r['remote_ip_prefix'] != normalized_ip_prefix:
|
||||||
|
LOG.info("remote_ip_prefix %(remote_ip_prefix)s configured in "
|
||||||
|
"rule %(rule_id)s is not normalized. Normalized CIDR "
|
||||||
|
"%(normalized_ip_prefix)s will be used instead.",
|
||||||
|
{'remote_ip_prefix': r['remote_ip_prefix'],
|
||||||
|
'rule_id': r['id'],
|
||||||
|
'normalized_ip_prefix': normalized_ip_prefix})
|
||||||
src_or_dst = 'src' if r['direction'] == const.INGRESS_DIRECTION else 'dst'
|
src_or_dst = 'src' if r['direction'] == const.INGRESS_DIRECTION else 'dst'
|
||||||
return ' && %s.%s == %s' % (ip_version, src_or_dst,
|
return ' && %s.%s == %s' % (ip_version, src_or_dst, normalized_ip_prefix)
|
||||||
r['remote_ip_prefix'])
|
|
||||||
|
|
||||||
|
|
||||||
def _get_protocol_number(protocol):
|
def _get_protocol_number(protocol):
|
||||||
@ -297,7 +310,7 @@ def add_acls_for_sg_port_group(ovn, security_group, txn):
|
|||||||
for r in security_group['security_group_rules']:
|
for r in security_group['security_group_rules']:
|
||||||
acl = _add_sg_rule_acl_for_port_group(
|
acl = _add_sg_rule_acl_for_port_group(
|
||||||
utils.ovn_port_group_name(security_group['id']), r)
|
utils.ovn_port_group_name(security_group['id']), r)
|
||||||
txn.add(ovn.pg_acl_add(**acl))
|
txn.add(ovn.pg_acl_add(**acl, may_exist=True))
|
||||||
|
|
||||||
|
|
||||||
def update_acls_for_security_group(plugin,
|
def update_acls_for_security_group(plugin,
|
||||||
@ -322,7 +335,7 @@ def update_acls_for_security_group(plugin,
|
|||||||
if not keep_name_severity:
|
if not keep_name_severity:
|
||||||
acl.pop('name')
|
acl.pop('name')
|
||||||
acl.pop('severity')
|
acl.pop('severity')
|
||||||
ovn.pg_acl_add(**acl).execute(check_error=True)
|
ovn.pg_acl_add(**acl, may_exist=True).execute(check_error=True)
|
||||||
else:
|
else:
|
||||||
ovn.pg_acl_del(acl['port_group'], acl['direction'],
|
ovn.pg_acl_del(acl['port_group'], acl['direction'],
|
||||||
acl['priority'], acl['match']).execute(
|
acl['priority'], acl['match']).execute(
|
||||||
|
@ -21,6 +21,7 @@ import signal
|
|||||||
import threading
|
import threading
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
import netaddr
|
||||||
from neutron_lib.api.definitions import portbindings
|
from neutron_lib.api.definitions import portbindings
|
||||||
from neutron_lib.api.definitions import segment as segment_def
|
from neutron_lib.api.definitions import segment as segment_def
|
||||||
from neutron_lib.callbacks import events
|
from neutron_lib.callbacks import events
|
||||||
@ -360,10 +361,40 @@ class OVNMechanismDriver(api.MechanismDriver):
|
|||||||
elif event == events.BEFORE_DELETE:
|
elif event == events.BEFORE_DELETE:
|
||||||
sg_rule = self._plugin.get_security_group_rule(
|
sg_rule = self._plugin.get_security_group_rule(
|
||||||
kwargs['context'], kwargs.get('security_group_rule_id'))
|
kwargs['context'], kwargs.get('security_group_rule_id'))
|
||||||
|
if sg_rule.get('remote_ip_prefix') is not None:
|
||||||
|
if self._sg_has_rules_with_same_normalized_cidr(sg_rule):
|
||||||
|
return
|
||||||
self._ovn_client.delete_security_group_rule(
|
self._ovn_client.delete_security_group_rule(
|
||||||
kwargs['context'],
|
kwargs['context'],
|
||||||
sg_rule)
|
sg_rule)
|
||||||
|
|
||||||
|
def _sg_has_rules_with_same_normalized_cidr(self, sg_rule):
|
||||||
|
compare_keys = [
|
||||||
|
'ethertype', 'direction', 'protocol',
|
||||||
|
'port_range_min', 'port_range_max']
|
||||||
|
sg_rules = self._plugin.get_security_group_rules(
|
||||||
|
n_context.get_admin_context(),
|
||||||
|
{'security_group_id': [sg_rule['security_group_id']]})
|
||||||
|
cidr_sg_rule = netaddr.IPNetwork(sg_rule['remote_ip_prefix'])
|
||||||
|
normalized_sg_rule_prefix = "%s/%s" % (cidr_sg_rule.network,
|
||||||
|
cidr_sg_rule.prefixlen)
|
||||||
|
|
||||||
|
def _rules_equal(rule1, rule2):
|
||||||
|
return not any(
|
||||||
|
rule1.get(key) != rule2.get(key) for key in compare_keys)
|
||||||
|
|
||||||
|
for rule in sg_rules:
|
||||||
|
if not rule.get('remote_ip_prefix') or rule['id'] == sg_rule['id']:
|
||||||
|
continue
|
||||||
|
cidr_rule = netaddr.IPNetwork(rule['remote_ip_prefix'])
|
||||||
|
normalized_rule_prefix = "%s/%s" % (cidr_rule.network,
|
||||||
|
cidr_rule.prefixlen)
|
||||||
|
if normalized_sg_rule_prefix != normalized_rule_prefix:
|
||||||
|
continue
|
||||||
|
if _rules_equal(sg_rule, rule):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _is_network_type_supported(self, network_type):
|
def _is_network_type_supported(self, network_type):
|
||||||
return (network_type in [const.TYPE_LOCAL,
|
return (network_type in [const.TYPE_LOCAL,
|
||||||
const.TYPE_FLAT,
|
const.TYPE_FLAT,
|
||||||
|
@ -303,6 +303,19 @@ class TestACLs(base.BaseTestCase):
|
|||||||
expected_match = ' && %s.dst == %s' % (ip_version, remote_ip_prefix)
|
expected_match = ' && %s.dst == %s' % (ip_version, remote_ip_prefix)
|
||||||
self.assertEqual(expected_match, match)
|
self.assertEqual(expected_match, match)
|
||||||
|
|
||||||
|
def test_acl_remote_ip_prefix_not_normalized(self):
|
||||||
|
sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({
|
||||||
|
'direction': 'ingress',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26'
|
||||||
|
}).info()
|
||||||
|
normalized_ip_prefix = '10.10.10.128/26'
|
||||||
|
ip_version = 'ip4'
|
||||||
|
|
||||||
|
match = ovn_acl.acl_remote_ip_prefix(sg_rule, ip_version)
|
||||||
|
expected_match = ' && %s.src == %s' % (ip_version,
|
||||||
|
normalized_ip_prefix)
|
||||||
|
self.assertEqual(expected_match, match)
|
||||||
|
|
||||||
def test_acl_remote_group_id(self):
|
def test_acl_remote_group_id(self):
|
||||||
sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({
|
sg_rule = fakes.FakeSecurityGroupRule.create_one_security_group_rule({
|
||||||
'direction': 'ingress',
|
'direction': 'ingress',
|
||||||
|
@ -183,12 +183,36 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
|
|||||||
|
|
||||||
@mock.patch.object(ovn_revision_numbers_db, 'bump_revision')
|
@mock.patch.object(ovn_revision_numbers_db, 'bump_revision')
|
||||||
def test__process_sg_rule_notifications_sgr_create(self, mock_bump):
|
def test__process_sg_rule_notifications_sgr_create(self, mock_bump):
|
||||||
with mock.patch.object(ovn_acl, 'update_acls_for_security_group') \
|
with mock.patch.object(
|
||||||
as ovn_acl_up:
|
self.mech_driver,
|
||||||
|
'_sg_has_rules_with_same_normalized_cidr') as has_same_rules, \
|
||||||
|
mock.patch.object(
|
||||||
|
ovn_acl, 'update_acls_for_security_group') as ovn_acl_up:
|
||||||
rule = {'security_group_id': 'sg_id'}
|
rule = {'security_group_id': 'sg_id'}
|
||||||
self.mech_driver._process_sg_rule_notification(
|
self.mech_driver._process_sg_rule_notification(
|
||||||
resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {},
|
resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {},
|
||||||
security_group_rule=rule, context=self.context)
|
security_group_rule=rule, context=self.context)
|
||||||
|
has_same_rules.assert_not_called()
|
||||||
|
ovn_acl_up.assert_called_once_with(
|
||||||
|
mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
'sg_id', rule, is_add_acl=True)
|
||||||
|
mock_bump.assert_called_once_with(
|
||||||
|
mock.ANY, rule, ovn_const.TYPE_SECURITY_GROUP_RULES)
|
||||||
|
|
||||||
|
@mock.patch.object(ovn_revision_numbers_db, 'bump_revision')
|
||||||
|
def test__process_sg_rule_notifications_sgr_create_with_remote_ip_prefix(
|
||||||
|
self, mock_bump):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.mech_driver,
|
||||||
|
'_sg_has_rules_with_same_normalized_cidr') as has_same_rules, \
|
||||||
|
mock.patch.object(
|
||||||
|
ovn_acl, 'update_acls_for_security_group') as ovn_acl_up:
|
||||||
|
rule = {'security_group_id': 'sg_id',
|
||||||
|
'remote_ip_prefix': '1.0.0.0/24'}
|
||||||
|
self.mech_driver._process_sg_rule_notification(
|
||||||
|
resources.SECURITY_GROUP_RULE, events.AFTER_CREATE, {},
|
||||||
|
security_group_rule=rule, context=self.context)
|
||||||
|
has_same_rules.assert_not_called()
|
||||||
ovn_acl_up.assert_called_once_with(
|
ovn_acl_up.assert_called_once_with(
|
||||||
mock.ANY, mock.ANY, mock.ANY,
|
mock.ANY, mock.ANY, mock.ANY,
|
||||||
'sg_id', rule, is_add_acl=True)
|
'sg_id', rule, is_add_acl=True)
|
||||||
@ -212,6 +236,59 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase):
|
|||||||
mock_delrev.assert_called_once_with(
|
mock_delrev.assert_called_once_with(
|
||||||
mock.ANY, rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
|
mock.ANY, rule['id'], ovn_const.TYPE_SECURITY_GROUP_RULES)
|
||||||
|
|
||||||
|
def test__sg_has_rules_with_same_normalized_cidr(self):
|
||||||
|
scenarios = [
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'tcp'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'udp'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'tcp'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'port_range_min': '2000', 'port_range_max': '2100'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '192.168.0.0/24',
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'port_range_min': '2000', 'port_range_max': '3000',
|
||||||
|
'direction': 'ingress'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'port_range_min': '2000', 'port_range_max': '3000',
|
||||||
|
'direction': 'egress'}, False),
|
||||||
|
({'id': 'rule-id', 'security_group_id': 'sec-group-uuid',
|
||||||
|
'remote_ip_prefix': '10.10.10.175/26',
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'port_range_min': '2000', 'port_range_max': '3000',
|
||||||
|
'direction': 'ingress'}, True)]
|
||||||
|
|
||||||
|
rules = [
|
||||||
|
{
|
||||||
|
'id': 'rule-1-id',
|
||||||
|
'protocol': 'udp',
|
||||||
|
}, {
|
||||||
|
'id': 'rule-2-id',
|
||||||
|
'remote_ip_prefix': '10.10.10.128/26',
|
||||||
|
'protocol': 'tcp',
|
||||||
|
'port_range_min': '2000',
|
||||||
|
'port_range_max': '3000',
|
||||||
|
'direction': 'ingress'
|
||||||
|
}]
|
||||||
|
|
||||||
|
with mock.patch.object(securitygroups_db.SecurityGroupDbMixin,
|
||||||
|
'get_security_group_rules',
|
||||||
|
return_value=rules):
|
||||||
|
for rule, expected_result in scenarios:
|
||||||
|
self.assertEqual(
|
||||||
|
expected_result,
|
||||||
|
self.mech_driver._sg_has_rules_with_same_normalized_cidr(
|
||||||
|
rule))
|
||||||
|
|
||||||
def test_port_invalid_binding_profile(self):
|
def test_port_invalid_binding_profile(self):
|
||||||
invalid_binding_profiles = [
|
invalid_binding_profiles = [
|
||||||
{'tag': 0,
|
{'tag': 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user