dragonflow/dragonflow/tests/fullstack/test_sg_flows.py

399 lines
17 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import time
from oslo_log import log
from dragonflow._i18n import _LI
from dragonflow.controller.common import constants as const
from dragonflow.tests.common import utils
from dragonflow.tests.fullstack import test_base
from dragonflow.tests.fullstack import test_objects as objects
LOG = log.getLogger(__name__)
class TestOVSFlowsForSecurityGroup(test_base.DFTestBase):
def _is_skip_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_CONNTRACK_TABLE
goto_table = const.INGRESS_DISPATCH_TABLE
else:
table = const.EGRESS_CONNTRACK_TABLE
goto_table = const.SERVICES_CLASSIFICATION_TABLE
if (flow['table'] == str(table)) and \
(flow['priority'] == str(const.PRIORITY_DEFAULT)) and \
(flow['actions'] == ('goto_table:' + str(goto_table))):
return True
return False
def _is_default_drop_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
(flow['priority'] == str(const.PRIORITY_DEFAULT)) and \
(flow['actions'] == 'drop'):
return True
return False
def _is_conntrack_pass_flow(self, flow, direction, ct_state_match):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
goto_table = const.INGRESS_DISPATCH_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
goto_table = const.SERVICES_CLASSIFICATION_TABLE
if (flow['table'] == str(table)) and \
(flow['priority'] == str(const.PRIORITY_CT_STATE)) and \
(ct_state_match in flow['match']) and \
(flow['actions'] == ('goto_table:' + str(goto_table))):
return True
return False
def _is_conntrack_established_pass_flow(self, flow, direction):
return self._is_conntrack_pass_flow(
flow=flow, direction=direction,
ct_state_match='-new+est-rel-inv+trk')
def _is_conntrack_relative_not_new_pass_flow(self, flow, direction):
return self._is_conntrack_pass_flow(
flow=flow, direction=direction,
ct_state_match='-new+rel-inv+trk')
def _is_conntrack_relative_new_pass_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
(flow['priority'] == str(const.PRIORITY_CT_STATE)) and \
('+new+rel-inv+trk' in flow['match']) and \
('ct(commit,table' in flow['actions']):
return True
return False
def _is_conntrack_invalid_drop_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
(flow['priority'] == str(const.PRIORITY_CT_STATE)) and \
('ct_state=+inv+trk' in flow['match']) and \
(flow['actions'] == 'drop'):
return True
return False
def _is_associating_flow(self, flow, direction, of_port, reg7):
if direction == 'ingress':
match = 'reg7=' + reg7
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
match = 'in_port=' + of_port
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
('ct_state=+new-est-rel-inv+trk' in flow['match']) and \
(match in flow['match']) and \
('conjunction(' in flow['actions']) and \
(',1/2)' in flow['actions']):
return True
return False
def _find_associating_flows(self, flows, of_port, reg7):
ingress_associating_flow = None
egress_associating_flow = None
for flow in flows:
if self._is_associating_flow(flow=flow, direction='ingress',
of_port=of_port, reg7=reg7):
ingress_associating_flow = flow
elif self._is_associating_flow(flow=flow, direction='egress',
of_port=of_port, reg7=reg7):
egress_associating_flow = flow
return ingress_associating_flow, egress_associating_flow
def _is_rule_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
('conjunction(' in flow['actions']) and \
(',2/2' in flow['actions']):
return True
return False
def _is_permit_flow(self, flow, direction):
if direction == 'ingress':
table = const.INGRESS_SECURITY_GROUP_TABLE
else:
table = const.EGRESS_SECURITY_GROUP_TABLE
if (flow['table'] == str(table)) and \
('conj_id=' in flow['match']) and \
('ct(commit,table' in flow['actions']):
return True
return False
def _check_rule_flows(self, flows, expect):
ingress_rule_flow_check = not expect
egress_rule_flow_check = not expect
ingress_permit_flow_check = not expect
egress_permit_flow_check = not expect
for flow in flows:
if self._is_rule_flow(flow, 'ingress'):
ingress_rule_flow_check = expect
elif self._is_rule_flow(flow, 'egress'):
egress_rule_flow_check = expect
elif self._is_permit_flow(flow, 'ingress'):
ingress_permit_flow_check = expect
elif self._is_permit_flow(flow, 'egress'):
egress_permit_flow_check = expect
self.assertTrue(ingress_rule_flow_check)
self.assertTrue(egress_rule_flow_check)
self.assertTrue(ingress_permit_flow_check)
self.assertTrue(egress_permit_flow_check)
def _get_vm_port(self, ip, mac):
ports = self.nb_api.get_all_logical_ports()
for port in ports:
if port.get_device_owner() == 'compute:None':
if port.get_ip() == ip and port.get_mac() == mac:
return port
return None
def _get_of_port(self, port_id):
ovsdb = utils.OvsDBParser()
return ovsdb.get_ofport(port_id)
def test_default_flows(self):
found_ingress_skip_flow = False
found_egress_skip_flow = False
found_ingress_default_drop_flow = False
found_egress_default_drop_flow = False
found_ingress_conntrack_established_pass_flow = False
found_egress_conntrack_established_pass_flow = False
found_ingress_conntrack_relative_not_new_pass_flow = False
found_egress_conntrack_relative_not_new_pass_flow = False
found_ingress_conntrack_relative_new_pass_flow = False
found_egress_conntrack_relative_new_pass_flow = False
found_ingress_conntrack_invalied_drop_flow = False
found_egress_conntrack_invalied_drop_flow = False
ovs = utils.OvsFlowsParser()
flows = ovs.dump()
for flow in flows:
if self._is_skip_flow(flow=flow, direction='ingress'):
found_ingress_skip_flow = True
elif self._is_skip_flow(flow=flow, direction='egress'):
found_egress_skip_flow = True
elif self._is_default_drop_flow(flow=flow, direction='ingress'):
found_ingress_default_drop_flow = True
elif self._is_default_drop_flow(flow=flow, direction='egress'):
found_egress_default_drop_flow = True
elif self._is_conntrack_established_pass_flow(flow=flow,
direction='ingress'):
found_ingress_conntrack_established_pass_flow = True
elif self._is_conntrack_established_pass_flow(flow=flow,
direction='egress'):
found_egress_conntrack_established_pass_flow = True
elif self._is_conntrack_relative_not_new_pass_flow(
flow=flow, direction='ingress'):
found_ingress_conntrack_relative_not_new_pass_flow = True
elif self._is_conntrack_relative_not_new_pass_flow(
flow=flow, direction='egress'):
found_egress_conntrack_relative_not_new_pass_flow = True
elif self._is_conntrack_relative_new_pass_flow(
flow=flow, direction='ingress'):
found_ingress_conntrack_relative_new_pass_flow = True
elif self._is_conntrack_relative_new_pass_flow(
flow=flow, direction='egress'):
found_egress_conntrack_relative_new_pass_flow = True
elif self._is_conntrack_invalid_drop_flow(flow=flow,
direction='ingress'):
found_ingress_conntrack_invalied_drop_flow = True
elif self._is_conntrack_invalid_drop_flow(flow=flow,
direction='egress'):
found_egress_conntrack_invalied_drop_flow = True
LOG.info(_LI("default flows are: %s"), ovs.get_ovs_flows())
self.assertTrue(found_ingress_skip_flow)
self.assertTrue(found_egress_skip_flow)
self.assertTrue(found_ingress_default_drop_flow)
self.assertTrue(found_egress_default_drop_flow)
self.assertTrue(found_ingress_conntrack_established_pass_flow)
self.assertTrue(found_egress_conntrack_established_pass_flow)
self.assertTrue(found_ingress_conntrack_relative_not_new_pass_flow)
self.assertTrue(found_egress_conntrack_relative_not_new_pass_flow)
self.assertTrue(found_ingress_conntrack_relative_new_pass_flow)
self.assertTrue(found_egress_conntrack_relative_new_pass_flow)
self.assertTrue(found_ingress_conntrack_invalied_drop_flow)
self.assertTrue(found_egress_conntrack_invalied_drop_flow)
def test_associating_flows(self):
network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api))
network_id = network.create(network={'name': 'test_network1'})
self.assertTrue(network.exists())
subnet_info = {'network_id': network_id,
'cidr': '192.168.123.0/24',
'gateway_ip': '192.168.123.1',
'ip_version': 4,
'name': 'test_subnet1',
'enable_dhcp': True}
subnet = self.store(objects.SubnetTestObj(self.neutron,
self.nb_api,
network_id=network_id))
subnet.create(subnet_info)
self.assertTrue(subnet.exists())
security_group = self.store(objects.SecGroupTestObj(
self.neutron,
self.nb_api))
security_group_id = security_group.create()
self.assertTrue(security_group.exists())
vm = self.store(objects.VMTestObj(self, self.neutron))
vm.create(network=network, security_groups=[security_group_id])
addresses = vm.server.addresses['test_network1']
self.assertIsNotNone(addresses)
ip = addresses[0]['addr']
self.assertIsNotNone(ip)
mac = addresses[0]['OS-EXT-IPS-MAC:mac_addr']
self.assertIsNotNone(mac)
port = utils.wait_until_is_and_return(
lambda: self._get_vm_port(ip, mac),
exception=Exception('No port assigned to VM')
)
tunnel_key = port.get_tunnel_key()
tunnel_key_hex = hex(tunnel_key)
of_port = self._get_of_port(port.get_id())
self.assertIsNotNone(of_port)
ovs = utils.OvsFlowsParser()
flows_after_change = ovs.dump()
# Check if the associating flows were installed.
ingress_associating_flow, egress_associating_flow = \
self._find_associating_flows(flows_after_change, of_port,
tunnel_key_hex)
LOG.info(_LI("flows after associating a port and a security group"
" are: %s"),
ovs.get_ovs_flows())
self.assertIsNotNone(ingress_associating_flow)
self.assertIsNotNone(egress_associating_flow)
vm.server.stop()
vm.close()
time.sleep(utils.DEFAULT_CMD_TIMEOUT)
flows_after_update = ovs.dump()
# Check if the associating flows were removed.
ingress_associating_flow, egress_associating_flow = \
self._find_associating_flows(flows_after_update, of_port,
tunnel_key_hex)
self.assertIsNone(ingress_associating_flow)
self.assertIsNone(egress_associating_flow)
def test_rule_flows(self):
network = self.store(objects.NetworkTestObj(self.neutron, self.nb_api))
network_id = network.create(network={'name': 'test_network2'})
self.assertTrue(network.exists())
subnet_info = {'network_id': network_id,
'cidr': '192.168.124.0/24',
'gateway_ip': '192.168.124.1',
'ip_version': 4,
'name': 'test_subnet4',
'enable_dhcp': True}
subnet = self.store(objects.SubnetTestObj(self.neutron,
self.nb_api,
network_id=network_id))
subnet.create(subnet_info)
security_group = self.store(objects.SecGroupTestObj(
self.neutron,
self.nb_api))
security_group_id = security_group.create()
self.assertTrue(security_group.exists())
ingress_rule_info = {'ethertype': 'IPv4',
'direction': 'ingress',
'protocol': 'tcp',
'port_range_min': '8000',
'port_range_max': '8100',
'remote_ip_prefix': '192.168.124.0/24'}
ingress_rule_id = security_group.rule_create(secrule=ingress_rule_info)
self.assertTrue(security_group.rule_exists(ingress_rule_id))
egress_rule_info = {'ethertype': 'IPv4',
'direction': 'egress',
'protocol': '17',
'port_range_min': '53',
'port_range_max': '53',
'remote_group_id': security_group_id}
egress_rule_id = security_group.rule_create(secrule=egress_rule_info)
self.assertTrue(security_group.rule_exists(egress_rule_id))
vm = self.store(objects.VMTestObj(self, self.neutron))
vm.create(network=network, security_groups=[security_group_id])
time.sleep(utils.DEFAULT_CMD_TIMEOUT)
ovs = utils.OvsFlowsParser()
flows_after_change = ovs.dump()
LOG.info(_LI("flows after adding rules are: %s"),
ovs.get_ovs_flows())
# Check if the rule flows were installed.
self._check_rule_flows(flows_after_change, True)
vm.server.stop()
vm.close()
# We can't guarantee that all rule flows have been deleted because
# those rule flows may be installed in other test cases for all
# test cases are running synchronously.
# time.sleep(utils.DEFAULT_CMD_TIMEOUT)
# flows_after_update = ovs.dump()
# self._check_rule_flows(flows_after_update, False)