aboutsummaryrefslogtreecommitdiffstats
path: root/networking_sfc/tests
diff options
context:
space:
mode:
Diffstat (limited to 'networking_sfc/tests')
-rw-r--r--networking_sfc/tests/__init__.py0
-rw-r--r--networking_sfc/tests/base.py134
-rw-r--r--networking_sfc/tests/unit/__init__.py0
-rw-r--r--networking_sfc/tests/unit/cli/__init__.py0
-rw-r--r--networking_sfc/tests/unit/cli/test_flow_classifier.py182
-rw-r--r--networking_sfc/tests/unit/cli/test_port_chain.py186
-rw-r--r--networking_sfc/tests/unit/cli/test_port_pair.py160
-rw-r--r--networking_sfc/tests/unit/cli/test_port_pair_group.py144
-rw-r--r--networking_sfc/tests/unit/db/__init__.py0
-rw-r--r--networking_sfc/tests/unit/db/test_flowclassifier_db.py677
-rw-r--r--networking_sfc/tests/unit/db/test_sfc_db.py1490
-rw-r--r--networking_sfc/tests/unit/extensions/__init__.py0
-rw-r--r--networking_sfc/tests/unit/extensions/test_flowclassifier.py603
-rw-r--r--networking_sfc/tests/unit/extensions/test_sfc.py751
-rw-r--r--networking_sfc/tests/unit/services/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/flowclassifier/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/flowclassifier/test_driver_manager.py158
-rw-r--r--networking_sfc/tests/unit/services/flowclassifier/test_plugin.py168
-rw-r--r--networking_sfc/tests/unit/services/sfc/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/sfc/agent/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/sfc/agent/test-agent.py4012
-rw-r--r--networking_sfc/tests/unit/services/sfc/common/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/sfc/common/test_ovs_ext_lib.py93
-rw-r--r--networking_sfc/tests/unit/services/sfc/drivers/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/sfc/drivers/ovs/__init__.py0
-rw-r--r--networking_sfc/tests/unit/services/sfc/test_driver_manager.py325
-rw-r--r--networking_sfc/tests/unit/services/sfc/test_plugin.py468
27 files changed, 9551 insertions, 0 deletions
diff --git a/networking_sfc/tests/__init__.py b/networking_sfc/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/__init__.py
diff --git a/networking_sfc/tests/base.py b/networking_sfc/tests/base.py
new file mode 100644
index 0000000..83fa3c9
--- /dev/null
+++ b/networking_sfc/tests/base.py
@@ -0,0 +1,134 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 contextlib
+import mock
+from oslo_utils import uuidutils
+
+from neutron.agent import securitygroups_rpc as sg_rpc
+from neutron.api import extensions as api_ext
+from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api as dhcp_rpc_log
+from neutron.api.v2 import resource as api_res_log
+from neutron.common import config as cfg
+from neutron.extensions import vlantransparent as vlan_log
+from neutron import manager
+from neutron.notifiers import nova as nova_log
+from neutron.plugins.ml2 import config
+from neutron.plugins.ml2 import db as ml2_db
+from neutron.plugins.ml2.drivers import type_flat
+from neutron.plugins.ml2.drivers import type_local
+from neutron.plugins.ml2.drivers import type_tunnel
+from neutron.plugins.ml2.drivers import type_vlan
+from neutron.plugins.ml2 import managers as ml2_manager
+from neutron.plugins.ml2 import plugin as ml2_plugin
+from neutron import quota as quota_log
+from neutron.scheduler import dhcp_agent_scheduler as dhcp_agent_log
+
+from neutron.tests import base as n_base
+from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_plugin
+
+
+class BaseTestCase(n_base.BaseTestCase):
+ pass
+
+
+class NeutronDbPluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase):
+ def setUp(self, plugin=None, service_plugins=None, ext_mgr=None):
+ self._mock_unncessary_logging()
+
+ if not plugin:
+ plugin = 'neutron.plugins.ml2.plugin.Ml2Plugin'
+ config.cfg.CONF.set_override('tenant_network_types', ['gre'],
+ group='ml2')
+ config.cfg.CONF.set_override(
+ 'tunnel_id_ranges', ['1:1000'], group='ml2_type_gre')
+ config.cfg.CONF.set_override(
+ 'mechanism_drivers', ['openvswitch'], group='ml2')
+ super(NeutronDbPluginV2TestCase, self).setUp(
+ ext_mgr=ext_mgr,
+ plugin=plugin,
+ service_plugins=service_plugins
+ )
+ self._tenant_id = uuidutils.generate_uuid()
+ self._network = self._make_network(
+ self.fmt, 'net1',
+ True)
+ self._subnet = self._make_subnet(
+ self.fmt, self._network, gateway='10.0.0.1',
+ cidr='10.0.0.0/24', ip_version=4
+ )
+
+ def _mock_unncessary_logging(self):
+ mock_log_sg_rpc_p = mock.patch.object(sg_rpc, 'LOG')
+ self.mock_log_sg_rpc = mock_log_sg_rpc_p.start()
+
+ mock_log_api_ext_p = mock.patch.object(api_ext, 'LOG')
+ self.mock_log_api_ext = mock_log_api_ext_p.start()
+
+ mock_log_dhcp_rpc_log_p = mock.patch.object(dhcp_rpc_log, 'LOG')
+ self.mock_log_dhcp_rpc_log = mock_log_dhcp_rpc_log_p.start()
+
+ mock_log_dhcp_rpc_log_p = mock.patch.object(dhcp_rpc_log, 'LOG')
+ self.mock_log_dhcp_rpc_log = mock_log_dhcp_rpc_log_p.start()
+
+ mock_log_api_res_log_p = mock.patch.object(api_res_log, 'LOG')
+ self.mock_log_api_res_log = mock_log_api_res_log_p.start()
+
+ mock_log_cfg_p = mock.patch.object(cfg, 'LOG')
+ self.mock_log_cfg = mock_log_cfg_p.start()
+
+ mock_log_vlan_log_p = mock.patch.object(vlan_log, 'LOG')
+ self.mock_log_vlan_log = mock_log_vlan_log_p.start()
+
+ mock_log_manager_p = mock.patch.object(manager, 'LOG')
+ self.mock_log_manager = mock_log_manager_p.start()
+
+ mock_log_nova_p = mock.patch.object(nova_log, 'LOG')
+ self.mock_log_nova = mock_log_nova_p.start()
+
+ mock_log_ml2_db_p = mock.patch.object(ml2_db, 'LOG')
+ self.mock_log_ml2_db = mock_log_ml2_db_p.start()
+
+ mock_log_ml2_manager_p = mock.patch.object(ml2_manager, 'LOG')
+ self.mock_log_ml2_manager = mock_log_ml2_manager_p.start()
+
+ mock_log_plugin_p = mock.patch.object(ml2_plugin, 'LOG')
+ self.mock_log_plugin = mock_log_plugin_p.start()
+
+ mock_log_type_flat_p = mock.patch.object(type_flat, 'LOG')
+ self.mock_log_type_flat = mock_log_type_flat_p.start()
+
+ mock_log_type_local_p = mock.patch.object(type_local, 'LOG')
+ self.mock_log_type_local = mock_log_type_local_p.start()
+
+ mock_log_type_tunnel_p = mock.patch.object(type_tunnel, 'LOG')
+ self.mock_log_type_tunnel = mock_log_type_tunnel_p.start()
+
+ mock_log_type_vlan_p = mock.patch.object(type_vlan, 'LOG')
+ self.mock_log_type_vlan = mock_log_type_vlan_p.start()
+
+ mock_log_quota_log_p = mock.patch.object(quota_log, 'LOG')
+ self.mock_log_quota_log = mock_log_quota_log_p.start()
+
+ mock_log_dhcp_agent_log_p = mock.patch.object(dhcp_agent_log, 'LOG')
+ self.mock_log_dhcp_agent_log = mock_log_dhcp_agent_log_p.start()
+
+ def tearDown(self):
+ super(NeutronDbPluginV2TestCase, self).tearDown()
+
+ @contextlib.contextmanager
+ def port(self, fmt=None, **kwargs):
+ net_id = self._network['network']['id']
+ port = self._make_port(fmt or self.fmt, net_id, **kwargs)
+ yield port
diff --git a/networking_sfc/tests/unit/__init__.py b/networking_sfc/tests/unit/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/__init__.py
diff --git a/networking_sfc/tests/unit/cli/__init__.py b/networking_sfc/tests/unit/cli/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/cli/__init__.py
diff --git a/networking_sfc/tests/unit/cli/test_flow_classifier.py b/networking_sfc/tests/unit/cli/test_flow_classifier.py
new file mode 100644
index 0000000..37c2142
--- /dev/null
+++ b/networking_sfc/tests/unit/cli/test_flow_classifier.py
@@ -0,0 +1,182 @@
+# Copyright 2015 Huawei Technologies India Pvt. Ltd.
+# All Rights Reserved
+#
+# 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 sys
+import uuid
+
+import mock
+
+from neutronclient import shell
+from neutronclient.tests.unit import test_cli20
+
+from networking_sfc.cli import flow_classifier as fc
+
+source_port_UUID = str(uuid.uuid4())
+destination_port_UUID = str(uuid.uuid4())
+
+
+class CLITestV20FCExtensionJSON(test_cli20.CLITestV20Base):
+ def setUp(self):
+ self._mock_extension_loading()
+ super(CLITestV20FCExtensionJSON, self).setUp(plurals={})
+ self.register_non_admin_status_resource('flow_classifier')
+
+ def _create_patch(self, name, func=None):
+ patcher = mock.patch(name)
+ thing = patcher.start()
+ self.addCleanup(patcher.stop)
+ return thing
+
+ def _mock_extension_loading(self):
+ ext_pkg = 'neutronclient.common.extension'
+ flow_classifier = self._create_patch(ext_pkg +
+ '._discover_via_entry_points')
+ flow_classifier.return_value = [("flow_classifier", fc)]
+ return flow_classifier
+
+ def test_ext_cmd_loaded(self):
+ shell.NeutronShell('2.0')
+ ext_cmd = {'flow-classifier-list': fc.FlowClassifierList,
+ 'flow-classifier-create': fc.FlowClassifierCreate,
+ 'flow-classifier-update': fc.FlowClassifierUpdate,
+ 'flow-classifier-delete': fc.FlowClassifierDelete,
+ 'flow-classifier-show': fc.FlowClassifierShow}
+ self.assertDictContainsSubset(ext_cmd, shell.COMMANDS['2.0'])
+
+ def test_create_flow_classifier_with_mandatory_params(self):
+ """create flow-classifier: flow1."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierCreate(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ name = 'flow1'
+ ethertype = 'IPv4'
+ args = [name, '--ethertype', ethertype]
+ position_names = ['name', 'ethertype']
+ position_values = [name, ethertype]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_flow_classifier_with_all_params(self):
+ """create flow-classifier: flow1."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierCreate(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ name = 'flow1'
+ protocol_name = 'TCP'
+ ethertype = 'IPv4'
+ source_port = '0:65535'
+ source_port_min = 0
+ source_port_max = 65535
+ destination_port = '1:65534'
+ destination_port_min = 1
+ destination_port_max = 65534
+ source_ip = '192.168.1.0/24'
+ destination_ip = '192.168.2.0/24'
+ logical_source_port = '4a334cd4-fe9c-4fae-af4b-321c5e2eb051'
+ logical_destination_port = '1278dcd4-459f-62ed-754b-87fc5e4a6751'
+ description = 'my-desc'
+ l7_param = "url=my_url"
+ l7_param_expected = {"url": "my_url"}
+ args = [name,
+ '--protocol', protocol_name,
+ '--ethertype', ethertype,
+ '--source-port', source_port,
+ '--destination-port', destination_port,
+ '--source-ip-prefix', source_ip,
+ '--destination-ip-prefix', destination_ip,
+ '--logical-source-port', logical_source_port,
+ '--logical-destination-port', logical_destination_port,
+ '--description', description,
+ '--l7-parameters', l7_param]
+ position_names = ['name', 'protocol', 'ethertype',
+ 'source_port_range_min', 'source_port_range_max',
+ 'destination_port_range_min',
+ 'destination_port_range_max',
+ 'source_ip_prefix', 'destination_ip_prefix',
+ 'logical_source_port', 'logical_destination_port',
+ 'description', 'l7_parameters']
+ position_values = [name, protocol_name, ethertype,
+ source_port_min, source_port_max,
+ destination_port_min, destination_port_max,
+ source_ip, destination_ip, logical_source_port,
+ logical_destination_port, description,
+ l7_param_expected]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_list_flow_classifier(self):
+ """List available flow-classifiers."""
+ resources = "flow_classifiers"
+ cmd = fc.FlowClassifierList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, True)
+
+ def test_list_flow_classifier_sort(self):
+ """flow_classifier-list --sort-key name --sort-key id --sort-key asc
+
+ --sort-key desc
+ """
+ resources = "flow_classifiers"
+ cmd = fc.FlowClassifierList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd,
+ sort_key=["name", "id"],
+ sort_dir=["asc", "desc"])
+
+ def test_list_flow_classifier_limit(self):
+ """flow-classifier-list -P."""
+ resources = "flow_classifiers"
+ cmd = fc.FlowClassifierList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, page_size=1000)
+
+ def test_show_flow_classifier_id(self):
+ """flow-classifier-show test_id."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id, args, ['id'])
+
+ def test_show_flow_classifier_id_name(self):
+ """flow-classifier-show ."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', '--fields', 'name', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id,
+ args, ['id', 'name'])
+
+ def test_update_flow_classifier_description(self):
+ """flow-classifier-update myid --name newname."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierUpdate(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid, '--description', 'flow_classifier1', '--description',
+ 'flow_classifier2']
+ updatefields = {'description': 'flow_classifier2'}
+ self._test_update_resource(resource, cmd, myid, args, updatefields)
+
+ def test_update_flow_classifier_name(self):
+ """flow-classifier-update myid --protocol any."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierUpdate(test_cli20.MyApp(sys.stdout), None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--name', 'myname'],
+ {'name': 'myname'})
+
+ def test_delete_flow_classifer(self):
+ """flow-classifier-delete my-id."""
+ resource = 'flow_classifier'
+ cmd = fc.FlowClassifierDelete(test_cli20.MyApp(sys.stdout), None)
+ my_id = 'myid1'
+ args = [my_id]
+ self._test_delete_resource(resource, cmd, my_id, args)
diff --git a/networking_sfc/tests/unit/cli/test_port_chain.py b/networking_sfc/tests/unit/cli/test_port_chain.py
new file mode 100644
index 0000000..0d834bd
--- /dev/null
+++ b/networking_sfc/tests/unit/cli/test_port_chain.py
@@ -0,0 +1,186 @@
+# Copyright 2015 Huawei Technologies India Pvt. Ltd.
+# All Rights Reserved
+#
+# 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 sys
+import uuid
+
+import mock
+
+from neutronclient import shell
+from neutronclient.tests.unit import test_cli20
+
+from networking_sfc.cli import port_chain as pc
+
+FAKE_port_pair_group1_UUID = str(uuid.uuid4())
+FAKE_port_pair_group2_UUID = str(uuid.uuid4())
+FAKE_FC1_UUID = str(uuid.uuid4())
+FAKE_FC2_UUID = str(uuid.uuid4())
+FAKE_PARAM1_UUID = str(uuid.uuid4())
+FAKE_PARAM2_UUID = str(uuid.uuid4())
+
+
+class CLITestV20PortChainExtensionJSON(test_cli20.CLITestV20Base):
+ def setUp(self):
+ self._mock_extension_loading()
+ super(CLITestV20PortChainExtensionJSON, self).setUp()
+ self.register_non_admin_status_resource('port_chain')
+
+ def _create_patch(self, name, func=None):
+ patcher = mock.patch(name)
+ thing = patcher.start()
+ self.addCleanup(patcher.stop)
+ return thing
+
+ def _mock_extension_loading(self):
+ ext_pkg = 'neutronclient.common.extension'
+ port_chain = self._create_patch(ext_pkg +
+ '._discover_via_entry_points')
+ port_chain.return_value = [("port_chain", pc)]
+ return port_chain
+
+ def test_ext_cmd_loaded(self):
+ shell.NeutronShell('2.0')
+ ext_cmd = {'port-chain-list': pc.PortChainList,
+ 'port-chain-create': pc.PortChainCreate,
+ 'port-chain-update': pc.PortChainUpdate,
+ 'port-chain-delete': pc.PortChainDelete,
+ 'port-chain-show': pc.PortChainShow}
+ self.assertDictContainsSubset(ext_cmd, shell.COMMANDS['2.0'])
+
+ def test_create_port_chain_with_mandatory_param(self):
+ """Create port_chain: myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainCreate(test_cli20.MyApp(sys.stdout),
+ None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair-group', FAKE_port_pair_group1_UUID]
+ position_names = ['name', 'port_pair_groups']
+ position_values = [name, [FAKE_port_pair_group1_UUID]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_chain_with_multiple_port_pair_group(self):
+ """Create port_chain: myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair-group', FAKE_port_pair_group1_UUID,
+ '--port-pair-group', FAKE_port_pair_group2_UUID]
+ position_names = ['name', 'port_pair_groups']
+ position_values = [name, [FAKE_port_pair_group1_UUID,
+ FAKE_port_pair_group2_UUID]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_chain_with_all_params(self):
+ """Create port_chain: myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ desc = 'check port chain cli'
+ chain_parameter = "correlation=mpls"
+ chain_parameter_expected = {"correlation": "mpls"}
+ args = [name, '--description', desc, '--port-pair-group',
+ FAKE_port_pair_group1_UUID, '--flow-classifier',
+ FAKE_FC1_UUID, '--chain-parameters', chain_parameter]
+ position_names = ['name', 'description', 'port_pair_groups',
+ 'flow_classifiers', 'chain_parameters']
+ position_values = [name, desc, [FAKE_port_pair_group1_UUID],
+ [FAKE_FC1_UUID], chain_parameter_expected]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_chain_with_single_classifier(self):
+ """Create port_chain: myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair-group', FAKE_port_pair_group1_UUID,
+ '--flow-classifier', FAKE_FC1_UUID]
+ position_names = ['name', 'port_pair_groups', 'flow_classifiers']
+ position_values = [name, [FAKE_port_pair_group1_UUID], [FAKE_FC1_UUID]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_chain_with_multiple_classifiers(self):
+ """Create port_chain: myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair-group', FAKE_port_pair_group1_UUID,
+ '--flow-classifier', FAKE_FC1_UUID,
+ '--flow-classifier', FAKE_FC2_UUID]
+ position_names = ['name', 'port_pair_groups', 'flow_classifiers']
+ position_values = [name, [FAKE_port_pair_group1_UUID], [FAKE_FC1_UUID,
+ FAKE_FC2_UUID]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_update_port_chain(self):
+ """Update port_chain: myid --name myname."""
+ resource = 'port_chain'
+ cmd = pc.PortChainUpdate(test_cli20.MyApp(sys.stdout), None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--name', 'myname'],
+ {'name': 'myname'})
+
+ def test_update_port_chain_with_no_flow_classifier(self):
+ """Update port_chain: myid --name myname --no-flow-classifier None."""
+ resource = 'port_chain'
+ cmd = pc.PortChainUpdate(test_cli20.MyApp(sys.stdout), None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--name', 'myname',
+ '--no-flow-classifier'],
+ {'name': 'myname',
+ 'flow_classifiers': []})
+
+ def test_delete_port_chain(self):
+ """Delete port-chain: myid."""
+ resource = 'port_chain'
+ cmd = pc.PortChainDelete(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid]
+ self._test_delete_resource(resource, cmd, myid, args)
+
+ def test_list_port_chain(self):
+ """List port_chain."""
+ resources = 'port_chains'
+ cmd = pc.PortChainList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, True)
+
+ def test_list_port_chains_sort(self):
+ """List port_chains: --sort-key name --sort-key id --sort-key asc
+
+ --sort-key desc
+ """
+ resources = "port_chains"
+ cmd = pc.PortChainList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd,
+ sort_key=["name", "id"],
+ sort_dir=["asc", "desc"])
+
+ def test_show_port_chain(self):
+ """Show port-chain: --fields id --fields name myid."""
+ resource = 'port_chain'
+ cmd = pc.PortChainShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', '--fields', 'name', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id,
+ args, ['id', 'name'])
diff --git a/networking_sfc/tests/unit/cli/test_port_pair.py b/networking_sfc/tests/unit/cli/test_port_pair.py
new file mode 100644
index 0000000..9a302e2
--- /dev/null
+++ b/networking_sfc/tests/unit/cli/test_port_pair.py
@@ -0,0 +1,160 @@
+# Copyright 2015 Huawei Technologies India Pvt. Ltd.
+# All Rights Reserved
+#
+# 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 sys
+import uuid
+
+import mock
+
+from neutronclient import shell
+from neutronclient.tests.unit import test_cli20
+
+from networking_sfc.cli import port_pair as pp
+
+ingress_port_UUID = str(uuid.uuid4())
+egress_port_UUID = str(uuid.uuid4())
+
+
+class CLITestV20PortPairExtensionJSON(test_cli20.CLITestV20Base):
+ def setUp(self):
+ self._mock_extension_loading()
+ super(CLITestV20PortPairExtensionJSON, self).setUp()
+ self.register_non_admin_status_resource('port_pair')
+
+ def _create_patch(self, name, func=None):
+ patcher = mock.patch(name)
+ thing = patcher.start()
+ self.addCleanup(patcher.stop)
+ return thing
+
+ def _mock_extension_loading(self):
+ ext_pkg = 'neutronclient.common.extension'
+ port_pair = self._create_patch(ext_pkg +
+ '._discover_via_entry_points')
+ port_pair.return_value = [("port_pair", pp)]
+ return port_pair
+
+ def test_ext_cmd_loaded(self):
+ shell.NeutronShell('2.0')
+ ext_cmd = {'port-pair-list': pp.PortPairList,
+ 'port-pair-create': pp.PortPairCreate,
+ 'port-pair-update': pp.PortPairUpdate,
+ 'port-pair-delete': pp.PortPairDelete,
+ 'port-pair-show': pp.PortPairShow}
+ self.assertDictContainsSubset(ext_cmd, shell.COMMANDS['2.0'])
+
+ def test_create_port_pair_with_mandatory_param(self):
+ """Create port_pair: myname."""
+ resource = 'port_pair'
+ cmd = pp.PortPairCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--ingress', ingress_port_UUID,
+ '--egress', egress_port_UUID]
+ position_names = ['name', 'ingress', 'egress']
+ position_values = [name, ingress_port_UUID, egress_port_UUID]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_group_with_bidirectional_port(self):
+ """Create port_pair: myname with bidirectional port."""
+ resource = 'port_pair'
+ cmd = pp.PortPairCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--ingress', ingress_port_UUID,
+ '--egress', ingress_port_UUID]
+ position_names = ['name', 'ingress', 'egress']
+ position_values = [name, ingress_port_UUID, ingress_port_UUID]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_pair_with_all_param(self):
+ """Create port_pair: myname with all parameter"""
+ resource = 'port_pair'
+ cmd = pp.PortPairCreate(test_cli20.MyApp(sys.stdout),
+ None)
+ name = 'myname'
+ myid = 'myid'
+ desc = "my_port_pair"
+ service_fn_param = 'correlation=None'
+ service_fn_param_exp = {"correlation": "None"}
+ args = [name, '--ingress', ingress_port_UUID,
+ '--egress', egress_port_UUID, '--description', desc,
+ '--service-function-parameters', service_fn_param]
+ position_names = ['name', 'ingress', 'egress', 'description',
+ 'service_function_parameters']
+ position_values = [name, ingress_port_UUID, egress_port_UUID, desc,
+ service_fn_param_exp]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_update_port_pair_description(self):
+ """Update port_pair: myid --name myname."""
+ resource = 'port_pair'
+ desc1 = "My_New_Port_Pair"
+ cmd = pp.PortPairUpdate(test_cli20.MyApp(sys.stdout), None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--description', desc1],
+ {'description': desc1})
+
+ def test_update_port_pair_name(self):
+ """Update port_pair: myid --name myname."""
+ resource = 'port_pair'
+ my_name = "My_New_Port_Pair"
+ cmd = pp.PortPairUpdate(test_cli20.MyApp(sys.stdout), None)
+ self._test_update_resource(resource, cmd, 'myid',
+ ['myid', '--name', my_name],
+ {'name': my_name})
+
+ def test_delete_port_pair(self):
+ """Delete port-pair: myid."""
+ resource = 'port_pair'
+ cmd = pp.PortPairDelete(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid]
+ self._test_delete_resource(resource, cmd, myid, args)
+
+ def test_list_port_pair(self):
+ """List port_pairs."""
+ resources = 'port_pairs'
+ cmd = pp.PortPairList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, True)
+
+ def test_list_port_pair_limit(self):
+ """size (1000) limited list: port-pair -P."""
+ resources = "port_pairs"
+ cmd = pp.PortPairList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, page_size=1000)
+
+ def test_list_port_pairs_sort(self):
+ """List port_pairs: --sort-key name --sort-key id --sort-key asc
+
+ --sort-key desc
+ """
+ resources = "port_pairs"
+ cmd = pp.PortPairList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd,
+ sort_key=["name", "id"],
+ sort_dir=["asc", "desc"])
+
+ def test_show_port_pair(self):
+ """Show port-pairs: --fields id --fields name myid."""
+ resource = 'port_pair'
+ cmd = pp.PortPairShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', '--fields', 'name', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id,
+ args, ['id', 'name'])
diff --git a/networking_sfc/tests/unit/cli/test_port_pair_group.py b/networking_sfc/tests/unit/cli/test_port_pair_group.py
new file mode 100644
index 0000000..f610ef0
--- /dev/null
+++ b/networking_sfc/tests/unit/cli/test_port_pair_group.py
@@ -0,0 +1,144 @@
+# Copyright 2015 Huawei Technologies India Pvt. Ltd.
+# All Rights Reserved
+#
+# 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 sys
+import uuid
+
+import mock
+
+from neutronclient import shell
+from neutronclient.tests.unit import test_cli20
+
+from networking_sfc.cli import port_pair_group as pg
+
+pp1 = str(uuid.uuid4())
+pp2 = str(uuid.uuid4())
+pp3 = str(uuid.uuid4())
+pp4 = str(uuid.uuid4())
+
+
+class CLITestV20PortGroupExtensionJSON(test_cli20.CLITestV20Base):
+ def setUp(self):
+ self._mock_extension_loading()
+ super(CLITestV20PortGroupExtensionJSON, self).setUp()
+ self.register_non_admin_status_resource('port_pair_group')
+
+ def _create_patch(self, name, func=None):
+ patcher = mock.patch(name)
+ thing = patcher.start()
+ self.addCleanup(patcher.stop)
+ return thing
+
+ def _mock_extension_loading(self):
+ ext_pkg = 'neutronclient.common.extension'
+ port_pair_group = self._create_patch(ext_pkg +
+ '._discover_via_entry_points')
+ port_pair_group.return_value = [("port_pair_group", pg)]
+ return port_pair_group
+
+ def test_ext_cmd_loaded(self):
+ shell.NeutronShell('2.0')
+ ext_cmd = {'port-pair-group-list': pg.PortPairGroupList,
+ 'port-pair-group-create': pg.PortPairGroupCreate,
+ 'port-pair-group-update': pg.PortPairGroupUpdate,
+ 'port-pair-group-delete': pg.PortPairGroupDelete,
+ 'port-pair-group-show': pg.PortPairGroupShow}
+ self.assertDictContainsSubset(ext_cmd, shell.COMMANDS['2.0'])
+
+ def test_create_port_pair_group_with_mandatory_args(self):
+ """Create port_pair_group: myname."""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair', pp1]
+ position_names = ['name', 'port_pairs']
+ position_values = [name, [pp1]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_create_port_pair_group_with_ingress_egress_port_group(self):
+ """Create port_pair_group: myname with multiple port pairs"""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupCreate(test_cli20.MyApp(sys.stdout), None)
+ name = 'myname'
+ myid = 'myid'
+ args = [name, '--port-pair', pp1, '--port-pair', pp2]
+ position_names = ['name', 'port_pairs']
+ position_values = [name, [pp1, pp2]]
+ self._test_create_resource(resource, cmd, name, myid, args,
+ position_names, position_values)
+
+ def test_delete_port_pair_group(self):
+ """Delete port_pair_group: myid."""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupDelete(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid]
+ self._test_delete_resource(resource, cmd, myid, args)
+
+ def test_update_port_group_only_port_pair(self):
+ """Update port_pair_group"""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupUpdate(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid, '--port-pair', pp1,
+ '--port-pair', pp2]
+ updatefields = {'port_pairs': [pp1, pp2]}
+ self._test_update_resource(resource, cmd, myid, args, updatefields)
+
+ def test_update_port_group_with_all_desc(self):
+ """Update port_pair_group and description"""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupUpdate(test_cli20.MyApp(sys.stdout), None)
+ myid = 'myid'
+ args = [myid, '--port-pair', pp1,
+ '--port-pair', pp2, '--description', 'my_port_group',
+ '--description', 'my_port_pair_group']
+ updatefields = {'port_pairs': [pp1, pp2],
+ 'description': 'my_port_pair_group'}
+ self._test_update_resource(resource, cmd, myid, args, updatefields)
+
+ def test_list_port_pair_group(self):
+ """List port_pair_group."""
+ resources = 'port_pair_groups'
+ cmd = pg.PortPairGroupList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, True)
+
+ def test_list_port_pair_group_limit(self):
+ """size (1000) limited list: port-pair-group -P."""
+ resources = "port_pair_groups"
+ cmd = pg.PortPairGroupList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd, page_size=1000)
+
+ def test_list_port_group_sort(self):
+ """List port_pair_group: --sort-key name --sort-key id --sort-key asc
+
+ --sort-key desc
+ """
+ resources = "port_pair_groups"
+ cmd = pg.PortPairGroupList(test_cli20.MyApp(sys.stdout), None)
+ self._test_list_resources(resources, cmd,
+ sort_key=["name", "id"],
+ sort_dir=["asc", "desc"])
+
+ def test_show_port_group(self):
+ """Show port-chain: --fields id --fields name myid."""
+ resource = 'port_pair_group'
+ cmd = pg.PortPairGroupShow(test_cli20.MyApp(sys.stdout), None)
+ args = ['--fields', 'id', '--fields', 'name', self.test_id]
+ self._test_show_resource(resource, cmd, self.test_id,
+ args, ['id', 'name'])
diff --git a/networking_sfc/tests/unit/db/__init__.py b/networking_sfc/tests/unit/db/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/db/__init__.py
diff --git a/networking_sfc/tests/unit/db/test_flowclassifier_db.py b/networking_sfc/tests/unit/db/test_flowclassifier_db.py
new file mode 100644
index 0000000..36c9af8
--- /dev/null
+++ b/networking_sfc/tests/unit/db/test_flowclassifier_db.py
@@ -0,0 +1,677 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 contextlib
+import mock
+import six
+import webob.exc
+
+from oslo_config import cfg
+from oslo_utils import importutils
+from oslo_utils import uuidutils
+
+from neutron.api import extensions as api_ext
+from neutron.common import config
+from neutron.common import constants as const
+import neutron.extensions as nextensions
+
+from networking_sfc.db import flowclassifier_db as fdb
+from networking_sfc import extensions
+from networking_sfc.extensions import flowclassifier as fc_ext
+from networking_sfc.tests import base
+
+
+DB_FLOWCLASSIFIER_PLUGIN_CLASS = (
+ "networking_sfc.db.flowclassifier_db.FlowClassifierDbPlugin"
+)
+extensions_path = ':'.join(extensions.__path__ + nextensions.__path__)
+
+
+class FlowClassifierDbPluginTestCaseBase(base.BaseTestCase):
+ def _create_flow_classifier(
+ self, fmt, flow_classifier=None, expected_res_status=None, **kwargs
+ ):
+ ctx = kwargs.get('context', None)
+ tenant_id = kwargs.get('tenant_id', self._tenant_id)
+ data = {'flow_classifier': flow_classifier or {}}
+ if ctx is None:
+ data['flow_classifier'].update({'tenant_id': tenant_id})
+ req = self.new_create_request(
+ 'flow_classifiers', data, fmt, context=ctx
+ )
+ res = req.get_response(self.ext_api)
+ if expected_res_status:
+ self.assertEqual(res.status_int, expected_res_status)
+ return res
+
+ @contextlib.contextmanager
+ def flow_classifier(
+ self, fmt=None, flow_classifier=None, do_delete=True, **kwargs
+ ):
+ if not fmt:
+ fmt = self.fmt
+ res = self._create_flow_classifier(fmt, flow_classifier, **kwargs)
+ if res.status_int >= 400:
+ raise webob.exc.HTTPClientError(code=res.status_int)
+ flow_classifier = self.deserialize(fmt or self.fmt, res)
+ yield flow_classifier
+ if do_delete:
+ self._delete('flow_classifiers',
+ flow_classifier['flow_classifier']['id'])
+
+ def _get_expected_flow_classifier(self, flow_classifier):
+ expected_flow_classifier = {
+ 'name': flow_classifier.get('name') or '',
+ 'description': flow_classifier.get('description') or '',
+ 'source_port_range_min': flow_classifier.get(
+ 'source_port_range_min'),
+ 'source_port_range_max': flow_classifier.get(
+ 'source_port_range_max'),
+ 'destination_port_range_min': flow_classifier.get(
+ 'destination_port_range_min'),
+ 'destination_port_range_max': flow_classifier.get(
+ 'destination_port_range_max'),
+ 'ethertype': flow_classifier.get(
+ 'ethertype') or 'IPv4',
+ 'protocol': flow_classifier.get(
+ 'protocol'),
+ 'l7_parameters': flow_classifier.get(
+ 'l7_parameters') or {}
+ }
+ if (
+ 'source_ip_prefix' in flow_classifier and
+ flow_classifier['source_ip_prefix']
+ ):
+ expected_flow_classifier['source_ip_prefix'] = (
+ flow_classifier['source_ip_prefix'])
+ if (
+ 'destination_ip_prefix' in flow_classifier and
+ flow_classifier['destination_ip_prefix']
+ ):
+ expected_flow_classifier['destination_ip_prefix'] = (
+ flow_classifier['destination_ip_prefix'])
+ return expected_flow_classifier
+
+ def _test_create_flow_classifier(
+ self, flow_classifier, expected_flow_classifier=None
+ ):
+ if expected_flow_classifier is None:
+ expected_flow_classifier = self._get_expected_flow_classifier(
+ flow_classifier)
+ with self.flow_classifier(flow_classifier=flow_classifier) as fc:
+ for k, v in six.iteritems(expected_flow_classifier):
+ self.assertIn(k, fc['flow_classifier'])
+ self.assertEqual(fc['flow_classifier'][k], v)
+
+
+class FlowClassifierDbPluginTestCase(
+ base.NeutronDbPluginV2TestCase,
+ FlowClassifierDbPluginTestCaseBase
+):
+ resource_prefix_map = dict(
+ (k, fc_ext.FLOW_CLASSIFIER_PREFIX)
+ for k in fc_ext.RESOURCE_ATTRIBUTE_MAP.keys()
+ )
+
+ def setUp(self, core_plugin=None, flowclassifier_plugin=None,
+ ext_mgr=None):
+ mock_log_p = mock.patch.object(fdb, 'LOG')
+ self.mock_log = mock_log_p.start()
+ cfg.CONF.register_opts(fc_ext.flow_classifier_quota_opts, 'QUOTAS')
+ if not flowclassifier_plugin:
+ flowclassifier_plugin = DB_FLOWCLASSIFIER_PLUGIN_CLASS
+ service_plugins = {
+ fc_ext.FLOW_CLASSIFIER_EXT: flowclassifier_plugin
+ }
+ fdb.FlowClassifierDbPlugin.supported_extension_aliases = [
+ fc_ext.FLOW_CLASSIFIER_EXT]
+ fdb.FlowClassifierDbPlugin.path_prefix = (
+ fc_ext.FLOW_CLASSIFIER_PREFIX
+ )
+ super(FlowClassifierDbPluginTestCase, self).setUp(
+ ext_mgr=ext_mgr,
+ plugin=core_plugin,
+ service_plugins=service_plugins
+ )
+ if not ext_mgr:
+ self.flowclassifier_plugin = importutils.import_object(
+ flowclassifier_plugin)
+ ext_mgr = api_ext.PluginAwareExtensionManager(
+ extensions_path,
+ {fc_ext.FLOW_CLASSIFIER_EXT: self.flowclassifier_plugin}
+ )
+ app = config.load_paste_app('extensions_test_app')
+ self.ext_api = api_ext.ExtensionMiddleware(app, ext_mgr=ext_mgr)
+
+ def test_create_flow_classifier(self):
+ self._test_create_flow_classifier({})
+
+ def test_quota_create_flow_classifier(self):
+ cfg.CONF.set_override('quota_flow_classifier', 3, group='QUOTAS')
+ self._create_flow_classifier(self.fmt, {}, expected_res_status=201)
+ self._create_flow_classifier(self.fmt, {}, expected_res_status=201)
+ self._create_flow_classifier(self.fmt, {}, expected_res_status=201)
+ self._create_flow_classifier(self.fmt, {}, expected_res_status=409)
+
+ def test_create_flow_classifier_with_all_fields(self):
+ self._test_create_flow_classifier({
+ 'name': 'test1',
+ 'ethertype': const.IPv4,
+ 'protocol': const.PROTO_NAME_TCP,
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200,
+ 'destination_port_range_min': 101,
+ 'destination_port_range_max': 201,
+ 'source_ip_prefix': '10.100.0.0/16',
+ 'destination_ip_prefix': '10.200.0.0/16',
+ 'logical_source_port': None,
+ 'logical_destination_port': None,
+ 'l7_parameters': {}
+ })
+
+ def test_create_flow_classifier_with_all_supported_ethertype(self):
+ self._test_create_flow_classifier({
+ 'ethertype': None
+ })
+ self._test_create_flow_classifier({
+ 'ethertype': 'IPv4'
+ })
+ self._test_create_flow_classifier({
+ 'ethertype': 'IPv6'
+ })
+
+ def test_create_flow_classifier_with_invalid_ethertype(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'ethertype': 'unsupported',
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_all_supported_protocol(self):
+ self._test_create_flow_classifier({
+ 'protocol': None
+ })
+ self._test_create_flow_classifier({
+ 'protocol': const.PROTO_NAME_TCP
+ })
+ self._test_create_flow_classifier({
+ 'protocol': const.PROTO_NAME_UDP
+ })
+ self._test_create_flow_classifier({
+ 'protocol': const.PROTO_NAME_ICMP
+ })
+
+ def test_create_flow_classifier_with_invalid_protocol(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'protocol': 'unsupported',
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_all_supported_port_protocol(self):
+ self._test_create_flow_classifier({
+ 'source_port_range_min': None,
+ 'source_port_range_max': None,
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None
+ })
+ self._test_create_flow_classifier({
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200,
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 200,
+ 'protocol': const.PROTO_NAME_TCP
+ })
+ self._test_create_flow_classifier({
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 100,
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 100,
+ 'protocol': const.PROTO_NAME_TCP
+ })
+ self._test_create_flow_classifier({
+ 'source_port_range_min': '100',
+ 'source_port_range_max': '200',
+ 'destination_port_range_min': '100',
+ 'destination_port_range_max': '200',
+ 'protocol': const.PROTO_NAME_UDP
+ }, {
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200,
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 200,
+ 'protocol': const.PROTO_NAME_UDP
+ })
+
+ def test_create_flow_classifier_with_invalid__port_protocol(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 'abc',
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_max': 'abc',
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 99,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 65536,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_max': 65536,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': -1,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_max': -1,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 'abc',
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_max': 'abc',
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 99,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 65536,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_max': 65536,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': -1,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_max': -1,
+ 'protocol': const.PROTO_NAME_TCP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 100
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_max': 100
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200,
+ 'protocol': const.PROTO_NAME_ICMP
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 100
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_max': 100
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 200
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 200,
+ 'protocol': const.PROTO_NAME_ICMP
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_all_supported_ip_prefix(self):
+ self._test_create_flow_classifier({
+ 'source_ip_prefix': None,
+ 'destination_ip_prefix': None
+ })
+ self._test_create_flow_classifier({
+ 'source_ip_prefix': '10.0.0.0/8',
+ 'destination_ip_prefix': '10.0.0.0/8'
+ })
+
+ def test_create_flow_classifier_with_invalid_ip_prefix(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_ip_prefix': '10.0.0.0/34'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_ip_prefix': '10.0.0.0.0/8'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_ip_prefix': '256.0.0.0/8'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'source_ip_prefix': '10.0.0.0'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_ip_prefix': '10.0.0.0/34'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_ip_prefix': '10.0.0.0.0/8'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_ip_prefix': '256.0.0.0/8'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'destination_ip_prefix': '10.0.0.0'
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_all_supported_l7_parameters(self):
+ self._test_create_flow_classifier({
+ 'l7_parameters': None
+ })
+ self._test_create_flow_classifier({
+ 'l7_parameters': {}
+ })
+
+ def test_create_flow_classifier_with_invalid_l7_parameters(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'l7_parameters': {'abc': 'def'}
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_port_id(self):
+ self._test_create_flow_classifier({
+ 'logical_source_port': None,
+ 'logical_destination_port': None,
+ })
+ with self.port(
+ name='test1'
+ ) as port:
+ self._test_create_flow_classifier({
+ 'logical_source_port': port['port']['id'],
+ 'logical_destination_port': port['port']['id'],
+ })
+
+ def test_create_flow_classifier_with_nouuid_port_id(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'logical_source_port': 'abc'
+ },
+ expected_res_status=400
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'logical_destination_port': 'abc'
+ },
+ expected_res_status=400
+ )
+
+ def test_create_flow_classifier_with_unknown_port_id(self):
+ self._create_flow_classifier(
+ self.fmt, {
+ 'logical_source_port': uuidutils.generate_uuid()
+ },
+ expected_res_status=404
+ )
+ self._create_flow_classifier(
+ self.fmt, {
+ 'logical_destination_port': uuidutils.generate_uuid()
+ },
+ expected_res_status=404
+ )
+
+ def test_list_flow_classifiers(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }) as fc1, self.flow_classifier(flow_classifier={
+ 'name': 'test2',
+ }) as fc2:
+ fcs = [fc1, fc2]
+ self._test_list_resources(
+ 'flow_classifier', fcs
+ )
+
+ def test_list_flow_classifiers_with_params(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }) as fc1, self.flow_classifier(flow_classifier={
+ 'name': 'test2',
+ }) as fc2:
+ self._test_list_resources(
+ 'flow_classifier', [fc1],
+ query_params='name=test1'
+ )
+ self._test_list_resources(
+ 'flow_classifier', [fc2],
+ query_params='name=test2'
+ )
+ self._test_list_resources(
+ 'flow_classifier', [],
+ query_params='name=test3'
+ )
+
+ def test_list_flow_classifiers_with_unknown_params(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }) as fc1, self.flow_classifier(flow_classifier={
+ 'name': 'test2',
+ }) as fc2:
+ self._test_list_resources(
+ 'flow_classifier', [fc1, fc2],
+ query_params='hello=test3'
+ )
+
+ def test_show_flow_classifier(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }) as fc:
+ req = self.new_show_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(fc['flow_classifier']):
+ self.assertEqual(res['flow_classifier'][k], v)
+
+ def test_show_flow_classifier_noexist(self):
+ req = self.new_show_request(
+ 'flow_classifiers', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_update_flow_classifier(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1',
+ 'description': 'desc1'
+ }) as fc:
+ updates = {
+ 'name': 'test2',
+ 'description': 'desc2',
+ }
+ req = self.new_update_request(
+ 'flow_classifiers', {'flow_classifier': updates},
+ fc['flow_classifier']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ expected = fc['flow_classifier']
+ expected.update(updates)
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['flow_classifier'][k], v)
+ req = self.new_show_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['flow_classifier'][k], v)
+
+ def _test_update_with_field(
+ self, fc, updates, expected_status_code
+ ):
+ req = self.new_update_request(
+ 'flow_classifiers', {'flow_classifier': updates},
+ fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, expected_status_code)
+
+ def test_update_flow_classifer_unsupported_fields(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1',
+ 'description': 'desc1'
+ }) as fc:
+ self._test_update_with_field(
+ fc, {'ethertype': None}, 400)
+ self._test_update_with_field(
+ fc, {'protocol': None}, 400)
+ self._test_update_with_field(
+ fc, {'source_port_range_min': None}, 400)
+ self._test_update_with_field(
+ fc, {'source_port_range_max': None}, 400)
+ self._test_update_with_field(
+ fc, {'destination_port_range_min': None}, 400)
+ self._test_update_with_field(
+ fc, {'destination_port_range_max': None}, 400)
+ self._test_update_with_field(
+ fc, {'source_ip_prefix': None}, 400)
+ self._test_update_with_field(
+ fc, {'destination_ip_prefix': None}, 400)
+ self._test_update_with_field(
+ fc, {'l7_parameters': None}, 400)
+
+ def test_delete_flow_classifier(self):
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }, do_delete=False) as fc:
+ req = self.new_delete_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ req = self.new_show_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_delete_flow_classifier_noexist(self):
+ req = self.new_delete_request(
+ 'flow_classifiers', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
diff --git a/networking_sfc/tests/unit/db/test_sfc_db.py b/networking_sfc/tests/unit/db/test_sfc_db.py
new file mode 100644
index 0000000..70d57e3
--- /dev/null
+++ b/networking_sfc/tests/unit/db/test_sfc_db.py
@@ -0,0 +1,1490 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 contextlib
+import mock
+import six
+import webob.exc
+
+from oslo_config import cfg
+from oslo_utils import importutils
+from oslo_utils import uuidutils
+
+from neutron.api import extensions as api_ext
+from neutron.common import config
+import neutron.extensions as nextensions
+
+from networking_sfc.db import flowclassifier_db as fdb
+from networking_sfc.db import sfc_db
+from networking_sfc import extensions
+from networking_sfc.extensions import flowclassifier as fc_ext
+from networking_sfc.extensions import sfc
+from networking_sfc.tests import base
+from networking_sfc.tests.unit.db import test_flowclassifier_db
+
+
+DB_SFC_PLUGIN_CLASS = (
+ "networking_sfc.db.sfc_db.SfcDbPlugin"
+)
+extensions_path = ':'.join(extensions.__path__ + nextensions.__path__)
+
+
+class SfcDbPluginTestCaseBase(
+ base.BaseTestCase
+):
+ def _create_port_chain(
+ self, fmt, port_chain=None, expected_res_status=None, **kwargs
+ ):
+ ctx = kwargs.get('context', None)
+ tenant_id = kwargs.get('tenant_id', self._tenant_id)
+ data = {'port_chain': port_chain or {}}
+ if ctx is None:
+ data['port_chain'].update({'tenant_id': tenant_id})
+ req = self.new_create_request(
+ 'port_chains', data, fmt, context=ctx
+ )
+ res = req.get_response(self.ext_api)
+ if expected_res_status:
+ self.assertEqual(res.status_int, expected_res_status)
+ return res
+
+ @contextlib.contextmanager
+ def port_chain(self, fmt=None, port_chain=None, do_delete=True, **kwargs):
+ if not fmt:
+ fmt = self.fmt
+ res = self._create_port_chain(fmt, port_chain, **kwargs)
+ if res.status_int >= 400:
+ raise webob.exc.HTTPClientError(code=res.status_int)
+ port_chain = self.deserialize(fmt or self.fmt, res)
+ yield port_chain
+ if do_delete:
+ self._delete('port_chains', port_chain['port_chain']['id'])
+
+ def _create_port_pair_group(
+ self, fmt, port_pair_group=None, expected_res_status=None, **kwargs
+ ):
+ ctx = kwargs.get('context', None)
+ tenant_id = kwargs.get('tenant_id', self._tenant_id)
+ data = {'port_pair_group': port_pair_group or {}}
+ if ctx is None:
+ data['port_pair_group'].update({'tenant_id': tenant_id})
+ req = self.new_create_request(
+ 'port_pair_groups', data, fmt, context=ctx
+ )
+ res = req.get_response(self.ext_api)
+ if expected_res_status:
+ self.assertEqual(res.status_int, expected_res_status)
+ return res
+
+ @contextlib.contextmanager
+ def port_pair_group(
+ self, fmt=None, port_pair_group=None, do_delete=True, **kwargs
+ ):
+ if not fmt:
+ fmt = self.fmt
+ res = self._create_port_pair_group(fmt, port_pair_group, **kwargs)
+ if res.status_int >= 400:
+ raise webob.exc.HTTPClientError(code=res.status_int)
+ port_pair_group = self.deserialize(fmt or self.fmt, res)
+ yield port_pair_group
+ if do_delete:
+ self._delete(
+ 'port_pair_groups',
+ port_pair_group['port_pair_group']['id'])
+
+ def _create_port_pair(
+ self, fmt, port_pair=None, expected_res_status=None, **kwargs
+ ):
+ ctx = kwargs.get('context', None)
+ tenant_id = kwargs.get('tenant_id', self._tenant_id)
+ data = {'port_pair': port_pair or {}}
+ if ctx is None:
+ data['port_pair'].update({'tenant_id': tenant_id})
+ req = self.new_create_request(
+ 'port_pairs', data, fmt, context=ctx
+ )
+ res = req.get_response(self.ext_api)
+ if expected_res_status:
+ self.assertEqual(res.status_int, expected_res_status)
+ return res
+
+ @contextlib.contextmanager
+ def port_pair(self, fmt=None, port_pair=None, do_delete=True, **kwargs):
+ if not fmt:
+ fmt = self.fmt
+ res = self._create_port_pair(fmt, port_pair, **kwargs)
+ if res.status_int >= 400:
+ raise webob.exc.HTTPClientError(code=res.status_int)
+ port_pair = self.deserialize(fmt or self.fmt, res)
+ yield port_pair
+ if do_delete:
+ self._delete('port_pairs', port_pair['port_pair']['id'])
+
+ def _get_expected_port_pair(self, port_pair):
+ return {
+ 'name': port_pair.get('name') or '',
+ 'description': port_pair.get('description') or '',
+ 'egress': port_pair.get('egress'),
+ 'ingress': port_pair.get('ingress'),
+ 'service_function_parameters': port_pair.get(
+ 'service_function_parameters') or {'correlation': None}
+ }
+
+ def _test_create_port_pair(self, port_pair, expected_port_pair=None):
+ if expected_port_pair is None:
+ expected_port_pair = self._get_expected_port_pair(port_pair)
+ with self.port_pair(port_pair=port_pair) as pp:
+ for k, v in six.iteritems(expected_port_pair):
+ self.assertEqual(pp['port_pair'][k], v)
+
+ def _test_create_port_pairs(
+ self, port_pairs, expected_port_pairs=None
+ ):
+ if port_pairs:
+ port_pair = port_pairs.pop()
+ if expected_port_pairs:
+ expected_port_pair = expected_port_pairs.pop()
+ else:
+ expected_port_pair = self._get_expected_port_pair(port_pair)
+ with self.port_pair(port_pair=port_pair) as pp:
+ for k, v in six.iteritems(expected_port_pair):
+ self.assertEqual(pp['port_pair'][k], v)
+
+ def _get_expected_port_pair_group(self, port_pair_group):
+ return {
+ 'name': port_pair_group.get('name') or '',
+ 'description': port_pair_group.get('description') or '',
+ 'port_pairs': port_pair_group.get('port_pairs') or []
+ }
+
+ def _test_create_port_pair_group(
+ self, port_pair_group, expected_port_pair_group=None
+ ):
+ if expected_port_pair_group is None:
+ expected_port_pair_group = self._get_expected_port_pair_group(
+ port_pair_group)
+ with self.port_pair_group(port_pair_group=port_pair_group) as pg:
+ for k, v in six.iteritems(expected_port_pair_group):
+ self.assertEqual(pg['port_pair_group'][k], v)
+
+ def _test_create_port_pair_groups(
+ self, port_pair_groups, expected_port_pair_groups=None
+ ):
+ if port_pair_groups:
+ port_pair_group = port_pair_groups.pop()
+ if expected_port_pair_groups:
+ expected_port_pair_group = expected_port_pair_groups.pop()
+ else:
+ expected_port_pair_group = self._get_expected_port_pair_group(
+ port_pair_group)
+ with self.port_pair_group(port_pair_group=port_pair_group) as pg:
+ for k, v in six.iteritems(expected_port_pair_group):
+ self.assertEqual(pg['port_pair_group'][k], v)
+
+ def _get_expected_port_chain(self, port_chain):
+ return {
+ 'name': port_chain.get('name') or '',
+ 'description': port_chain.get('description') or '',
+ 'port_pair_groups': port_chain['port_pair_groups'],
+ 'flow_classifiers': port_chain.get('flow_classifiers') or [],
+ 'chain_parameters': port_chain.get(
+ 'chain_parameters') or {'correlation': 'mpls'}
+ }
+
+ def _test_create_port_chain(self, port_chain, expected_port_chain=None):
+ if expected_port_chain is None:
+ expected_port_chain = self._get_expected_port_chain(port_chain)
+ with self.port_chain(port_chain=port_chain) as pc:
+ for k, v in six.iteritems(expected_port_chain):
+ self.assertEqual(pc['port_chain'][k], v)
+
+ def _test_create_port_chains(
+ self, port_chains, expected_port_chains=None
+ ):
+ if port_chains:
+ port_chain = port_chains.pop()
+ if expected_port_chains:
+ expected_port_chain = expected_port_chains.pop()
+ else:
+ expected_port_chain = self._get_expected_port_chain(
+ port_chain)
+ with self.port_chain(port_chain=port_chain) as pc:
+ for k, v in six.iteritems(expected_port_chain):
+ self.assertEqual(pc['port_chain'][k], v)
+
+
+class SfcDbPluginTestCase(
+ base.NeutronDbPluginV2TestCase,
+ test_flowclassifier_db.FlowClassifierDbPluginTestCaseBase,
+ SfcDbPluginTestCaseBase
+):
+ resource_prefix_map = dict([
+ (k, sfc.SFC_PREFIX)
+ for k in sfc.RESOURCE_ATTRIBUTE_MAP.keys()
+ ] + [
+ (k, fc_ext.FLOW_CLASSIFIER_PREFIX)
+ for k in fc_ext.RESOURCE_ATTRIBUTE_MAP.keys()
+ ])
+
+ def setUp(self, core_plugin=None, sfc_plugin=None,
+ flowclassifier_plugin=None, ext_mgr=None):
+ mock_log_p = mock.patch.object(sfc_db, 'LOG')
+ self.mock_log = mock_log_p.start()
+ cfg.CONF.register_opts(sfc.sfc_quota_opts, 'QUOTAS')
+ if not sfc_plugin:
+ sfc_plugin = DB_SFC_PLUGIN_CLASS
+ if not flowclassifier_plugin:
+ flowclassifier_plugin = (
+ test_flowclassifier_db.DB_FLOWCLASSIFIER_PLUGIN_CLASS)
+
+ service_plugins = {
+ sfc.SFC_EXT: sfc_plugin,
+ fc_ext.FLOW_CLASSIFIER_EXT: flowclassifier_plugin
+ }
+ sfc_db.SfcDbPlugin.supported_extension_aliases = [
+ "sfc"]
+ sfc_db.SfcDbPlugin.path_prefix = sfc.SFC_PREFIX
+ fdb.FlowClassifierDbPlugin.supported_extension_aliases = [
+ "flow_classifier"]
+ fdb.FlowClassifierDbPlugin.path_prefix = (
+ fc_ext.FLOW_CLASSIFIER_PREFIX
+ )
+ super(SfcDbPluginTestCase, self).setUp(
+ ext_mgr=ext_mgr,
+ plugin=core_plugin,
+ service_plugins=service_plugins
+ )
+ if not ext_mgr:
+ self.sfc_plugin = importutils.import_object(sfc_plugin)
+ self.flowclassifier_plugin = importutils.import_object(
+ flowclassifier_plugin)
+ ext_mgr = api_ext.PluginAwareExtensionManager(
+ extensions_path,
+ {
+ sfc.SFC_EXT: self.sfc_plugin,
+ fc_ext.FLOW_CLASSIFIER_EXT: self.flowclassifier_plugin
+ }
+ )
+ app = config.load_paste_app('extensions_test_app')
+ self.ext_api = api_ext.ExtensionMiddleware(app, ext_mgr=ext_mgr)
+
+ def test_create_port_chain(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'port_pair_groups': [pg['port_pair_group']['id']]})
+
+ def test_quota_create_port_chain(self):
+ cfg.CONF.set_override('quota_port_chain', 3, group='QUOTAS')
+ with self.port_pair_group(
+ port_pair_group={}, do_delete=False
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}, do_delete=False
+ ) as pg2, self.port_pair_group(
+ port_pair_group={}, do_delete=False
+ ) as pg3, self.port_pair_group(
+ port_pair_group={}, do_delete=False
+ ) as pg4:
+ self._create_port_chain(
+ self.fmt, {
+ 'port_pair_groups': [pg1['port_pair_group']['id']]
+ }, expected_res_status=201)
+ self._create_port_chain(
+ self.fmt, {
+ 'port_pair_groups': [pg2['port_pair_group']['id']]
+ }, expected_res_status=201)
+ self._create_port_chain(
+ self.fmt, {
+ 'port_pair_groups': [pg3['port_pair_group']['id']]
+ }, expected_res_status=201)
+ self._create_port_chain(
+ self.fmt, {
+ 'port_pair_groups': [pg4['port_pair_group']['id']]
+ }, expected_res_status=409)
+
+ def test_create_port_chain_all_fields(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'port_pair_groups': [pg['port_pair_group']['id']],
+ 'flow_classifiers': [],
+ 'name': 'abc',
+ 'description': 'def',
+ 'chain_parameters': {'correlation': 'mpls'}
+ })
+
+ def test_create_port_chain_multi_port_pair_groups(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ self._test_create_port_chain({
+ 'port_pair_groups': [
+ pg1['port_pair_group']['id'],
+ pg2['port_pair_group']['id']
+ ]
+ })
+
+ def test_create_port_chain_shared_port_pair_groups(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2, self.port_pair_group(
+ port_pair_group={}
+ ) as pg3:
+ self._test_create_port_chains([{
+ 'port_pair_groups': [
+ pg1['port_pair_group']['id'],
+ pg2['port_pair_group']['id']
+ ]
+ }, {
+ 'port_pair_groups': [
+ pg1['port_pair_group']['id'],
+ pg3['port_pair_group']['id']
+ ]
+ }])
+
+ def test_create_port_chain_shared_port_pair_groups_different_order(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ self._test_create_port_chains([{
+ 'port_pair_groups': [
+ pg1['port_pair_group']['id'],
+ pg2['port_pair_group']['id']
+ ]
+ }, {
+ 'port_pair_groups': [
+ pg2['port_pair_group']['id'],
+ pg1['port_pair_group']['id']
+ ]
+ }])
+
+ def test_create_port_chain_with_empty_chain_parameters(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'chain_parameters': {},
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_none_chain_parameters(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'chain_parameters': None,
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_default_chain_parameters(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'chain_parameters': {'correlation': 'mpls'},
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_none_flow_classifiers(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'flow_classifiers': None,
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_empty_flow_classifiers(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'flow_classifiers': [],
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_flow_classifiers(self):
+ with self.flow_classifier(flow_classifier={}) as fc:
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'flow_classifiers': [fc['flow_classifier']['id']],
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_multi_flow_classifiers(self):
+ with self.flow_classifier(
+ flow_classifier={}
+ ) as fc1, self.flow_classifier(
+ flow_classifier={}
+ ) as fc2:
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._test_create_port_chain({
+ 'flow_classifiers': [
+ fc1['flow_classifier']['id'],
+ fc2['flow_classifier']['id']
+ ],
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ })
+
+ def test_create_port_chain_with_port_pairs(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pp1, self.port_pair(port_pair={
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pp2:
+ with self.port_pair_group(port_pair_group={
+ 'port_pairs': [
+ pp1['port_pair']['id']
+ ]
+ }) as pg1, self.port_pair_group(port_pair_group={
+ 'port_pairs': [
+ pp2['port_pair']['id']
+ ]
+ }) as pg2:
+ self._test_create_port_chain({
+ 'port_pair_groups': [
+ pg1['port_pair_group']['id'],
+ pg2['port_pair_group']['id']
+ ]
+ })
+
+ def test_create_port_chain_with_empty_port_pair_groups(self):
+ self._create_port_chain(
+ self.fmt, {'port_pair_groups': []},
+ expected_res_status=400
+ )
+
+ def test_create_port_chain_with_nonuuid_port_pair_group_id(self):
+ self._create_port_chain(
+ self.fmt, {'port_pair_groups': ['unknown']},
+ expected_res_status=400
+ )
+
+ def test_create_port_chain_with_unknown_port_pair_group_id(self):
+ self._create_port_chain(
+ self.fmt, {'port_pair_groups': [uuidutils.generate_uuid()]},
+ expected_res_status=404
+ )
+
+ def test_create_port_chain_with_same_port_pair_groups(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg:
+ with self.port_chain(
+ port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }
+ ):
+ self._create_port_chain(
+ self.fmt, {
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, expected_res_status=409
+ )
+
+ def test_create_port_chain_with_no_port_pair_groups(self):
+ self._create_port_chain(
+ self.fmt, {}, expected_res_status=400
+ )
+
+ def test_create_port_chain_with_invalid_chain_parameters(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._create_port_chain(
+ self.fmt, {
+ 'chain_parameters': {'correlation': 'unknown'},
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, expected_res_status=400
+ )
+
+ def test_create_port_chain_unknown_flow_classifiers(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._create_port_chain(
+ self.fmt, {
+ 'flow_classifiers': [uuidutils.generate_uuid()],
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, expected_res_status=404
+ )
+
+ def test_create_port_chain_nouuid_flow_classifiers(self):
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._create_port_chain(
+ self.fmt, {
+ 'flow_classifiers': ['unknown'],
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, expected_res_status=400
+ )
+
+ def test_list_port_chains(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg1['port_pair_group']['id']]
+ }) as pc1, self.port_chain(port_chain={
+ 'port_pair_groups': [pg2['port_pair_group']['id']]
+ }) as pc2:
+ port_chains = [pc1, pc2]
+ self._test_list_resources(
+ 'port_chain', port_chains
+ )
+
+ def test_list_port_chains_with_params(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg1['port_pair_group']['id']]
+ }) as pc1, self.port_chain(port_chain={
+ 'name': 'test2',
+ 'port_pair_groups': [pg2['port_pair_group']['id']]
+ }) as pc2:
+ self._test_list_resources(
+ 'port_chain', [pc1],
+ query_params='name=test1'
+ )
+ self._test_list_resources(
+ 'port_chain', [pc2],
+ query_params='name=test2'
+ )
+ self._test_list_resources(
+ 'port_chain', [],
+ query_params='name=test3'
+ )
+
+ def test_list_port_chains_with_unknown_params(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg1['port_pair_group']['id']]
+ }) as pc1, self.port_chain(port_chain={
+ 'name': 'test2',
+ 'port_pair_groups': [pg2['port_pair_group']['id']]
+ }) as pc2:
+ self._test_list_resources(
+ 'port_chain', [pc1, pc2],
+ query_params='hello=test3'
+ )
+
+ def test_show_port_chain(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'description': 'portchain',
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }) as pc:
+ req = self.new_show_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ expected = self._get_expected_port_chain(pc['port_chain'])
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_chain'][k], v)
+
+ def test_show_port_chain_noexist(self):
+ req = self.new_show_request(
+ 'port_chains', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_update_port_chain(self):
+ with self.flow_classifier(
+ flow_classifier={}
+ ) as fc1, self.flow_classifier(
+ flow_classifier={}
+ ) as fc2:
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'port_pair_groups': [pg['port_pair_group']['id']],
+ 'flow_classifiers': [fc1['flow_classifier']['id']]
+ }) as pc:
+ updates = {
+ 'name': 'test2',
+ 'description': 'desc2',
+ 'flow_classifiers': [fc2['flow_classifier']['id']]
+ }
+ req = self.new_update_request(
+ 'port_chains', {'port_chain': updates},
+ pc['port_chain']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ expected = pc['port_chain']
+ expected.update(updates)
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_chain'][k], v)
+ req = self.new_show_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_chain'][k], v)
+
+ def test_update_port_chain_port_pair_groups(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg1, self.port_pair_group(
+ port_pair_group={}
+ ) as pg2:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg1['port_pair_group']['id']],
+ }) as pc:
+ updates = {
+ 'port_pair_groups': [pg2['port_pair_group']['id']]
+ }
+ req = self.new_update_request(
+ 'port_chains', {'port_chain': updates},
+ pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 400)
+
+ def test_update_port_chain_chain_parameters(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']],
+ }) as pc:
+ updates = {
+ 'chain_parameters': {'correlation': 'mpls'}
+ }
+ req = self.new_update_request(
+ 'port_chains', {'port_chain': updates},
+ pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 400)
+
+ def test_delete_port_chain(self):
+ with self.port_pair_group(
+ port_pair_group={}
+ ) as pg:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ req = self.new_show_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+ req = self.new_show_request(
+ 'port_pair_groups', pg['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 200)
+
+ def test_delete_port_chain_noexist(self):
+ req = self.new_delete_request(
+ 'port_chains', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_delete_flow_classifier_port_chain_exist(self):
+ with self.flow_classifier(flow_classifier={
+ }) as fc:
+ with self.port_pair_group(port_pair_group={
+ }) as pg:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']],
+ 'flow_classifiers': [fc['flow_classifier']['id']]
+ }):
+ req = self.new_delete_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 409)
+
+ def test_create_port_pair_group(self):
+ self._test_create_port_pair_group({})
+
+ def test_quota_create_port_pair_group_quota(self):
+ cfg.CONF.set_override('quota_port_pair_group', 3, group='QUOTAS')
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': []}, expected_res_status=201
+ )
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': []}, expected_res_status=201
+ )
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': []}, expected_res_status=201
+ )
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': []}, expected_res_status=409
+ )
+
+ def test_create_port_pair_group_all_fields(self):
+ self._test_create_port_pair_group({
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'port_pairs': []
+ })
+
+ def test_create_port_pair_group_with_port_pairs(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pp1, self.port_pair(port_pair={
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pp2:
+ self._test_create_port_pair_group({
+ 'port_pairs': [
+ pp1['port_pair']['id'],
+ pp2['port_pair']['id']
+ ]
+ })
+
+ def test_create_port_pair_group_with_nouuid_port_pair_id(self):
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': ['unknown']},
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_group_with_unknown_port_pair_id(self):
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': [uuidutils.generate_uuid()]},
+ expected_res_status=404
+ )
+
+ def test_create_port_pair_group_share_port_pair_id(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pp:
+ with self.port_pair_group(port_pair_group={
+ 'port_pairs': [pp['port_pair']['id']]
+ }):
+ self._create_port_pair_group(
+ self.fmt, {'port_pairs': [pp['port_pair']['id']]},
+ expected_res_status=409
+ )
+
+ def test_list_port_pair_groups(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc1, self.port_pair_group(port_pair_group={
+ 'name': 'test2'
+ }) as pc2:
+ port_pair_groups = [pc1, pc2]
+ self._test_list_resources(
+ 'port_pair_group', port_pair_groups
+ )
+
+ def test_list_port_pair_groups_with_params(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc1, self.port_pair_group(port_pair_group={
+ 'name': 'test2'
+ }) as pc2:
+ self._test_list_resources(
+ 'port_pair_group', [pc1],
+ query_params='name=test1'
+ )
+ self._test_list_resources(
+ 'port_pair_group', [pc2],
+ query_params='name=test2'
+ )
+ self._test_list_resources(
+ 'port_pair_group', [],
+ query_params='name=test3'
+ )
+
+ def test_list_port_pair_groups_with_unknown_params(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc1, self.port_pair_group(port_pair_group={
+ 'name': 'test2'
+ }) as pc2:
+ self._test_list_resources(
+ 'port_pair_group', [pc1, pc2],
+ query_params='hello=test3'
+ )
+
+ def test_show_port_pair_group(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc:
+ req = self.new_show_request(
+ 'port_pair_groups', pc['port_pair_group']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(pc['port_pair_group']):
+ self.assertEqual(res['port_pair_group'][k], v)
+
+ def test_show_port_pair_group_noexist(self):
+ req = self.new_show_request(
+ 'port_pair_groups', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_update_port_pair_group(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pp1, self.port_pair(port_pair={
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pp2:
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'port_pairs': [pp1['port_pair']['id']]
+ }) as pg:
+ updates = {
+ 'name': 'test2',
+ 'description': 'desc2',
+ 'port_pairs': [pp2['port_pair']['id']]
+ }
+ req = self.new_update_request(
+ 'port_pair_groups', {'port_pair_group': updates},
+ pg['port_pair_group']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ expected = pg['port_pair_group']
+ expected.update(updates)
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_pair_group'][k], v)
+ req = self.new_show_request(
+ 'port_pair_groups', pg['port_pair_group']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_pair_group'][k], v)
+
+ def test_delete_port_pair_group(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pair_groups', pc['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ req = self.new_show_request(
+ 'port_pair_groups', pc['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_delete_port_pair_group_port_chain_exist(self):
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pg:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }):
+ req = self.new_delete_request(
+ 'port_pair_groups', pg['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 409)
+
+ def test_delete_port_pair_group_noexist(self):
+ req = self.new_delete_request(
+ 'port_pair_groups', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_create_port_pair(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ self._test_create_port_pair({
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ })
+
+ def test_quota_create_port_pair_quota(self):
+ cfg.CONF.set_override('quota_port_pair', 3, group='QUOTAS')
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port1, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port1, self.port(
+ name='port3',
+ device_id='default'
+ ) as src_port2, self.port(
+ name='port4',
+ device_id='default'
+ ) as dst_port2, self.port(
+ name='port5',
+ device_id='default'
+ ) as src_port3, self.port(
+ name='port6',
+ device_id='default'
+ ) as dst_port3, self.port(
+ name='port7',
+ device_id='default'
+ ) as src_port4, self.port(
+ name='port8',
+ device_id='default'
+ ) as dst_port4:
+ self._create_port_pair(
+ self.fmt, {
+ 'ingress': src_port1['port']['id'],
+ 'egress': dst_port1['port']['id']
+ }, expected_res_status=201)
+ self._create_port_pair(
+ self.fmt, {
+ 'ingress': src_port2['port']['id'],
+ 'egress': dst_port2['port']['id']
+ }, expected_res_status=201)
+ self._create_port_pair(
+ self.fmt, {
+ 'ingress': src_port3['port']['id'],
+ 'egress': dst_port3['port']['id']
+ }, expected_res_status=201)
+ self._create_port_pair(
+ self.fmt, {
+ 'ingress': src_port4['port']['id'],
+ 'egress': dst_port4['port']['id']
+ }, expected_res_status=409)
+
+ def test_create_port_pair_all_fields(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ self._test_create_port_pair({
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id'],
+ 'service_function_parameters': {'correlation': None}
+ })
+
+ def test_create_port_pair_none_service_function_parameters(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ self._test_create_port_pair({
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id'],
+ 'service_function_parameters': None
+ })
+
+ def test_create_port_pair_empty_service_function_parameters(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ self._test_create_port_pair({
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id'],
+ 'service_function_parameters': {}
+ })
+
+ def test_create_port_pair_with_src_dst_same_port(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_dst_port:
+ self._test_create_port_pair({
+ 'ingress': src_dst_port['port']['id'],
+ 'egress': src_dst_port['port']['id']
+ })
+
+ def test_create_port_pair_empty_input(self):
+ self._create_port_pair(self.fmt, {}, expected_res_status=400)
+
+ def test_create_port_pair_with_no_ingress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'egress': dst_port['port']['id']
+ },
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_with_no_egress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_port['port']['id']
+ },
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_with_nouuid_ingress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': '1',
+ 'egress': dst_port['port']['id']
+ },
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_with_unknown_ingress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': uuidutils.generate_uuid(),
+ 'egress': dst_port['port']['id']
+ },
+ expected_res_status=404
+ )
+
+ def test_create_port_pair_with_nouuid_egress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_port['port']['id'],
+ 'egress': '1'
+ },
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_with_unkown_egress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_port['port']['id'],
+ 'egress': uuidutils.generate_uuid()
+ },
+ expected_res_status=404
+ )
+
+ def test_create_port_pair_ingress_egress_different_hosts(self):
+ with self.port(
+ name='port1',
+ device_id='device1'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='device2'
+ ) as dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ },
+ expected_res_status=400
+ )
+
+ def test_create_port_pair_with_invalid_service_function_parameters(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_dst_port['port']['id'],
+ 'egress': src_dst_port['port']['id'],
+ 'service_function_parameters': {'abc': 'def'}
+ },
+ expected_res_status=400
+ )
+
+ def test_list_port_pairs(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc1, self.port_pair(port_pair={
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pc2:
+ port_pairs = [pc1, pc2]
+ self._test_list_resources(
+ 'port_pair', port_pairs
+ )
+
+ def test_list_port_pairs_with_params(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc1, self.port_pair(port_pair={
+ 'name': 'test2',
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pc2:
+ self._test_list_resources(
+ 'port_pair', [pc1],
+ query_params='name=test1'
+ )
+ self._test_list_resources(
+ 'port_pair', [pc2],
+ query_params='name=test2'
+ )
+ self._test_list_resources(
+ 'port_pair', [],
+ query_params='name=test3'
+ )
+
+ def test_list_port_pairs_with_unknown_params(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc1, self.port_pair(port_pair={
+ 'name': 'test2',
+ 'ingress': dst_port['port']['id'],
+ 'egress': src_port['port']['id']
+ }) as pc2:
+ port_pairs = [pc1, pc2]
+ self._test_list_resources(
+ 'port_pair', port_pairs,
+ query_params='hello=test3'
+ )
+
+ def test_show_port_pair(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ req = self.new_show_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(pc['port_pair']):
+ self.assertEqual(res['port_pair'][k], v)
+
+ def test_show_port_pair_noexist(self):
+ req = self.new_show_request(
+ 'port_pairs', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_update_port_pair(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ updates = {
+ 'name': 'test2',
+ 'description': 'desc2'
+ }
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': updates},
+ pc['port_pair']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ expected = pc['port_pair']
+ expected.update(updates)
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_pair'][k], v)
+ req = self.new_show_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = self.deserialize(
+ self.fmt, req.get_response(self.ext_api)
+ )
+ for k, v in six.iteritems(expected):
+ self.assertEqual(res['port_pair'][k], v)
+
+ def test_update_port_pair_service_function_parameters(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ updates = {
+ 'service_function_parameters': {
+ 'correlation': None
+ }
+ }
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': updates},
+ pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 400)
+
+ def test_update_port_pair_ingress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ updates = {
+ 'ingress': dst_port['port']['id']
+ }
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': updates},
+ pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 400)
+
+ def test_update_port_pair_egress(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'description': 'desc1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ updates = {
+ 'egress': src_port['port']['id']
+ }
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': updates},
+ pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 400)
+
+ def test_delete_port_pair(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ req = self.new_show_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_delete_port_pair_noexist(self):
+ req = self.new_delete_request(
+ 'port_pairs', '1'
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 404)
+
+ def test_delete_port_pair_port_pair_group_exist(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pp:
+ with self.port_pair_group(port_pair_group={
+ 'port_pairs': [pp['port_pair']['id']]
+ }):
+ req = self.new_delete_request(
+ 'port_pairs', pp['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 409)
+
+ def test_delete_ingress_port_pair_exist(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }):
+ req = self.new_delete_request(
+ 'ports', src_port['port']['id']
+ )
+ res = req.get_response(self.api)
+ self.assertEqual(res.status_int, 500)
+
+ def test_delete_egress_port_pair_exist(self):
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }):
+ req = self.new_delete_request(
+ 'ports', dst_port['port']['id']
+ )
+ res = req.get_response(self.api)
+ self.assertEqual(res.status_int, 500)
diff --git a/networking_sfc/tests/unit/extensions/__init__.py b/networking_sfc/tests/unit/extensions/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/extensions/__init__.py
diff --git a/networking_sfc/tests/unit/extensions/test_flowclassifier.py b/networking_sfc/tests/unit/extensions/test_flowclassifier.py
new file mode 100644
index 0000000..7026ac5
--- /dev/null
+++ b/networking_sfc/tests/unit/extensions/test_flowclassifier.py
@@ -0,0 +1,603 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 copy
+import mock
+from webob import exc
+import webtest
+
+from oslo_utils import uuidutils
+
+from neutron.api.v2 import resource as api_res_log
+from neutron.common import config
+from neutron import manager
+from neutron.notifiers import nova as nova_log
+
+from neutron.tests.unit.api.v2 import test_base as test_api_v2
+from neutron.tests.unit.extensions import base as test_api_v2_extension
+
+from networking_sfc.extensions import flowclassifier as fc_ext
+
+_uuid = uuidutils.generate_uuid
+_get_path = test_api_v2._get_path
+
+FLOW_CLASSIFIER_PATH = (fc_ext.FLOW_CLASSIFIER_PREFIX[1:] + '/' +
+ fc_ext.FLOW_CLASSIFIER_EXT + 's')
+
+
+class FlowClassifierExtensionTestCase(
+ test_api_v2_extension.ExtensionTestCase
+):
+ fmt = 'json'
+
+ def setUp(self):
+ self._mock_unncessary_logging()
+ super(FlowClassifierExtensionTestCase, self).setUp()
+ self._setUpExtension(
+ 'networking_sfc.extensions.flowclassifier.'
+ 'FlowClassifierPluginBase',
+ fc_ext.FLOW_CLASSIFIER_EXT,
+ fc_ext.RESOURCE_ATTRIBUTE_MAP,
+ fc_ext.Flowclassifier,
+ fc_ext.FLOW_CLASSIFIER_PREFIX[1:],
+ plural_mappings={}
+ )
+
+ def _mock_unncessary_logging(self):
+ mock_log_cfg_p = mock.patch.object(config, 'LOG')
+ self.mock_log_cfg = mock_log_cfg_p.start()
+
+ mock_log_manager_p = mock.patch.object(manager, 'LOG')
+ self.mock_log_manager = mock_log_manager_p.start()
+
+ mock_log_nova_p = mock.patch.object(nova_log, 'LOG')
+ self.mock_log_nova = mock_log_nova_p.start()
+
+ mock_log_api_res_log_p = mock.patch.object(api_res_log, 'LOG')
+ self.mock_log_api_res_log = mock_log_api_res_log_p.start()
+
+ def _get_expected_flow_classifier(self, data):
+ source_port_range_min = data['flow_classifier'].get(
+ 'source_port_range_min')
+ if source_port_range_min is not None:
+ source_port_range_min = int(source_port_range_min)
+ source_port_range_max = data['flow_classifier'].get(
+ 'source_port_range_max')
+ if source_port_range_max is not None:
+ source_port_range_max = int(source_port_range_max)
+ destination_port_range_min = data['flow_classifier'].get(
+ 'destination_port_range_min')
+ if destination_port_range_min is not None:
+ destination_port_range_min = int(destination_port_range_min)
+ destination_port_range_max = data['flow_classifier'].get(
+ 'destination_port_range_max')
+ if destination_port_range_max is not None:
+ destination_port_range_max = int(destination_port_range_max)
+
+ return {'flow_classifier': {
+ 'name': data['flow_classifier'].get('name') or '',
+ 'description': data['flow_classifier'].get('description') or '',
+ 'tenant_id': data['flow_classifier']['tenant_id'],
+ 'source_port_range_min': source_port_range_min,
+ 'source_port_range_max': source_port_range_max,
+ 'destination_port_range_min': destination_port_range_min,
+ 'destination_port_range_max': destination_port_range_max,
+ 'l7_parameters': data['flow_classifier'].get(
+ 'l7_parameters') or {},
+ 'destination_ip_prefix': data['flow_classifier'].get(
+ 'destination_ip_prefix'),
+ 'source_ip_prefix': data['flow_classifier'].get(
+ 'source_ip_prefix'),
+ 'logical_source_port': data['flow_classifier'].get(
+ 'logical_source_port'),
+ 'logical_destination_port': data['flow_classifier'].get(
+ 'logical_destination_port'),
+ 'ethertype': data['flow_classifier'].get(
+ 'ethertype') or 'IPv4',
+ 'protocol': data['flow_classifier'].get(
+ 'protocol')
+ }}
+
+ def _clean_expected_flow_classifier(self, expected_flow_classifier):
+ if 'logical_source_port' in expected_flow_classifier:
+ del expected_flow_classifier['logical_source_port']
+ if 'logical_destination_port' in expected_flow_classifier:
+ del expected_flow_classifier['logical_destination_port']
+
+ def test_create_flow_classifier(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'tenant_id': _uuid(),
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_port_string(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'source_port_range_min': '100',
+ 'source_port_range_max': '200',
+ 'destination_port_range_min': '100',
+ 'destination_port_range_max': '200',
+ 'tenant_id': _uuid(),
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_ip_prefix_with_mask(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'source_ip_prefix': '10.0.0.0/8',
+ 'tenant_id': _uuid(),
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_non_l7_parameters(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'tenant_id': _uuid(),
+ 'l7_parameters': None
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_default_ethertype(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'tenant_id': _uuid(),
+ 'ethertype': 'IPv4'
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_all_fields(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'name': 'test1',
+ 'description': 'desc',
+ 'tenant_id': _uuid(),
+ 'source_port_range_min': 100,
+ 'source_port_range_max': 200,
+ 'destination_port_range_min': 100,
+ 'destination_port_range_max': 200,
+ 'l7_parameters': {},
+ 'destination_ip_prefix': '10.0.0.0/8',
+ 'source_ip_prefix': '10.0.0.0/8',
+ 'logical_source_port': _uuid(),
+ 'logical_destination_port': _uuid(),
+ 'ethertype': None,
+ 'protocol': None
+ }}
+ expected_data = self._get_expected_flow_classifier(data)
+ return_value = copy.copy(expected_data['flow_classifier'])
+ return_value.update({'id': flowclassifier_id})
+ self._clean_expected_flow_classifier(return_value)
+ instance = self.plugin.return_value
+ instance.create_flow_classifier.return_value = return_value
+ res = self.api.post(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_flow_classifier.assert_called_with(
+ mock.ANY,
+ flow_classifier=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_create_flow_classifier_invalid_l7_parameters(self):
+ data = {'flow_classifier': {
+ 'l7_parameters': {'abc': 'def'},
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_invalid_protocol(self):
+ data = {'flow_classifier': {
+ 'protocol': 'unknown',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_invalid_ethertype(self):
+ data = {'flow_classifier': {
+ 'ethertype': 'unknown',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_port_small(self):
+ data = {'flow_classifier': {
+ 'source_port_range_min': -1,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_port_large(self):
+ data = {'flow_classifier': {
+ 'source_port_range_min': 65536,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_ip_prefix_no_cidr(self):
+ data = {'flow_classifier': {
+ 'source_ip_prefix': '10.0.0.0',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_ip_prefix_invalid_cidr(self):
+ data = {'flow_classifier': {
+ 'source_ip_prefix': '10.0.0.0/33',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_flow_classifier_port_id_nouuid(self):
+ data = {'flow_classifier': {
+ 'logical_source_port': 'unknown',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_list(self):
+ flowclassifier_id = _uuid()
+ return_value = [{
+ 'tenant_id': _uuid(),
+ 'id': flowclassifier_id
+ }]
+ instance = self.plugin.return_value
+ instance.get_flow_classifiers.return_value = return_value
+
+ res = self.api.get(
+ _get_path(FLOW_CLASSIFIER_PATH, fmt=self.fmt))
+
+ instance.get_flow_classifiers.assert_called_with(
+ mock.ANY,
+ fields=mock.ANY,
+ filters=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifiers', res)
+ self.assertEqual(res['flow_classifiers'], return_value)
+
+ def test_flow_classifier_get(self):
+ flowclassifier_id = _uuid()
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': flowclassifier_id
+ }
+
+ instance = self.plugin.return_value
+ instance.get_flow_classifier.return_value = return_value
+
+ res = self.api.get(
+ _get_path(
+ FLOW_CLASSIFIER_PATH,
+ id=flowclassifier_id, fmt=self.fmt
+ )
+ )
+
+ instance.get_flow_classifier.assert_called_with(
+ mock.ANY,
+ flowclassifier_id,
+ fields=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(return_value, res['flow_classifier'])
+
+ def test_flow_classifier_update(self):
+ flowclassifier_id = _uuid()
+ update_data = {'flow_classifier': {
+ 'name': 'new_name',
+ 'description': 'new_desc',
+ }}
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': flowclassifier_id
+ }
+
+ instance = self.plugin.return_value
+ instance.update_flow_classifier.return_value = return_value
+
+ res = self.api.put(
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(update_data))
+
+ instance.update_flow_classifier.assert_called_with(
+ mock.ANY, flowclassifier_id,
+ flow_classifier=update_data)
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(res['flow_classifier'], return_value)
+
+ def test_flow_classifier_update_source_port_range_min(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'source_port_range_min': 100,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_source_port_range_max(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'source_port_range_max': 100,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_destination_port_range_min(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'destination_port_range_min': 100,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_destination_port_range_max(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'destination_port_range_max': 100,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_source_ip_prefix(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'source_ip_prefix': '10.0.0.0/8',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_destination_ip_prefix(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'destination_ip_prefix': '10.0.0.0/8',
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_logical_source_port(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'logical_source_port': _uuid(),
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_logical_destination_port(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'logical_destination_port': _uuid(),
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_ethertype(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'ethertype': None,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_protocol(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'protococol': None,
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_update_l7_parameters(self):
+ flowclassifier_id = _uuid()
+ data = {'flow_classifier': {
+ 'l7_parameters': {},
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(FLOW_CLASSIFIER_PATH, id=flowclassifier_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_flow_classifier_delete(self):
+ self._test_entity_delete('flow_classifier')
diff --git a/networking_sfc/tests/unit/extensions/test_sfc.py b/networking_sfc/tests/unit/extensions/test_sfc.py
new file mode 100644
index 0000000..01b7d8c
--- /dev/null
+++ b/networking_sfc/tests/unit/extensions/test_sfc.py
@@ -0,0 +1,751 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 copy
+import mock
+from webob import exc
+import webtest
+
+from oslo_utils import uuidutils
+
+from neutron.api.v2 import resource as api_res_log
+from neutron.common import config as cfg
+from neutron import manager
+from neutron.notifiers import nova as nova_log
+
+from neutron.tests.unit.api.v2 import test_base as test_api_v2
+from neutron.tests.unit.extensions import base as test_api_v2_extension
+
+from networking_sfc.extensions import sfc as sfc_ext
+
+_uuid = uuidutils.generate_uuid
+_get_path = test_api_v2._get_path
+
+PORT_CHAIN_PATH = (sfc_ext.SFC_PREFIX[1:] + '/port_chains')
+PORT_PAIR_PATH = (sfc_ext.SFC_PREFIX[1:] + '/port_pairs')
+PORT_PAIR_GROUP_PATH = (sfc_ext.SFC_PREFIX[1:] + '/port_pair_groups')
+
+
+class SfcExtensionTestCase(test_api_v2_extension.ExtensionTestCase):
+ fmt = 'json'
+
+ def setUp(self):
+ self._mock_unncessary_looging()
+ super(SfcExtensionTestCase, self).setUp()
+ self._setUpExtension(
+ 'networking_sfc.extensions.sfc.SfcPluginBase',
+ sfc_ext.SFC_EXT,
+ sfc_ext.RESOURCE_ATTRIBUTE_MAP,
+ sfc_ext.Sfc,
+ sfc_ext.SFC_PREFIX[1:],
+ plural_mappings={}
+ )
+
+ def _mock_unncessary_looging(self):
+ mock_log_cfg_p = mock.patch.object(cfg, 'LOG')
+ self.mock_log_cfg = mock_log_cfg_p.start()
+
+ mock_log_manager_p = mock.patch.object(manager, 'LOG')
+ self.mock_log_manager = mock_log_manager_p.start()
+
+ mock_log_nova_p = mock.patch.object(nova_log, 'LOG')
+ self.mock_log_nova = mock_log_nova_p.start()
+
+ mock_log_api_res_log_p = mock.patch.object(api_res_log, 'LOG')
+ self.mock_log_api_res_log = mock_log_api_res_log_p.start()
+
+ def _get_expected_port_chain(self, data):
+ return {'port_chain': {
+ 'description': data['port_chain'].get('description') or '',
+ 'name': data['port_chain'].get('name') or '',
+ 'port_pair_groups': data['port_chain']['port_pair_groups'],
+ 'chain_parameters': data['port_chain'].get(
+ 'chain_parameters') or {'correlation': 'mpls'},
+ 'flow_classifiers': data['port_chain'].get(
+ 'flow_classifiers') or [],
+ 'tenant_id': data['port_chain']['tenant_id']
+ }}
+
+ def test_create_port_chain(self):
+ portchain_id = _uuid()
+ data = {'port_chain': {
+ 'port_pair_groups': [_uuid()],
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_chain(data)
+ return_value = copy.copy(expected_data['port_chain'])
+ return_value.update({'id': portchain_id})
+ instance = self.plugin.return_value
+ instance.create_port_chain.return_value = return_value
+ res = self.api.post(_get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_chain.assert_called_with(
+ mock.ANY,
+ port_chain=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(return_value, res['port_chain'])
+
+ def test_create_port_chain_all_fields(self):
+ portchain_id = _uuid()
+ data = {'port_chain': {
+ 'description': 'desc',
+ 'name': 'test1',
+ 'port_pair_groups': [_uuid()],
+ 'chain_parameters': {'correlation': 'mpls'},
+ 'flow_classifiers': [],
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_chain(data)
+ return_value = copy.copy(expected_data['port_chain'])
+ return_value.update({'id': portchain_id})
+ instance = self.plugin.return_value
+ instance.create_port_chain.return_value = return_value
+ res = self.api.post(_get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_chain.assert_called_with(
+ mock.ANY,
+ port_chain=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(return_value, res['port_chain'])
+
+ def test_create_port_chain_none_chain_parameters(self):
+ portchain_id = _uuid()
+ data = {'port_chain': {
+ 'port_pair_groups': [_uuid()],
+ 'chain_parameters': None,
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_chain(data)
+ return_value = copy.copy(expected_data['port_chain'])
+ return_value.update({'id': portchain_id})
+ instance = self.plugin.return_value
+ instance.create_port_chain.return_value = return_value
+ res = self.api.post(_get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_chain.assert_called_with(
+ mock.ANY,
+ port_chain=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(return_value, res['port_chain'])
+
+ def test_create_port_chain_empty_chain_parameters(self):
+ portchain_id = _uuid()
+ data = {'port_chain': {
+ 'port_pair_groups': [_uuid()],
+ 'chain_parameters': {},
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_chain(data)
+ return_value = copy.copy(expected_data['port_chain'])
+ return_value.update({'id': portchain_id})
+ instance = self.plugin.return_value
+ instance.create_port_chain.return_value = return_value
+ res = self.api.post(_get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_chain.assert_called_with(
+ mock.ANY,
+ port_chain=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(return_value, res['port_chain'])
+
+ def test_create_port_chain_empty_port_pair_groups(self):
+ data = {'port_chain': {
+ 'port_pair_groups': [],
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_port_chain_nonuuid_port_pair_groups(self):
+ data = {'port_chain': {
+ 'port_pair_groups': ['nouuid'],
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_port_chain_nonuuid_flow_classifiers(self):
+ data = {'port_chain': {
+ 'port_pair_groups': [_uuid()],
+ 'flow_classifiers': ['nouuid'],
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_port_chain_invalid_chain_parameters(self):
+ data = {'port_chain': {
+ 'port_pair_groups': [_uuid()],
+ 'chain_parameters': {'abc': 'def'},
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_CHAIN_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_chain_list(self):
+ portchain_id = _uuid()
+ return_value = [{
+ 'tenant_id': _uuid(),
+ 'id': portchain_id
+ }]
+ instance = self.plugin.return_value
+ instance.get_port_chains.return_value = return_value
+
+ res = self.api.get(_get_path(PORT_CHAIN_PATH, fmt=self.fmt))
+
+ instance.get_port_chains.assert_called_with(
+ mock.ANY,
+ fields=mock.ANY,
+ filters=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chains', res)
+ self.assertEqual(res['port_chains'], return_value)
+
+ def test_port_chain_get(self):
+ portchain_id = _uuid()
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portchain_id
+ }
+
+ instance = self.plugin.return_value
+ instance.get_port_chain.return_value = return_value
+
+ res = self.api.get(_get_path(PORT_CHAIN_PATH,
+ id=portchain_id, fmt=self.fmt))
+
+ instance.get_port_chain.assert_called_with(
+ mock.ANY,
+ portchain_id,
+ fields=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(return_value, res['port_chain'])
+
+ def test_port_chain_update(self):
+ portchain_id = _uuid()
+ update_data = {'port_chain': {
+ 'name': 'new_name',
+ 'description': 'new_desc',
+ 'flow_classifiers': [_uuid()]
+ }}
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portchain_id
+ }
+
+ instance = self.plugin.return_value
+ instance.update_port_chain.return_value = return_value
+
+ res = self.api.put(_get_path(PORT_CHAIN_PATH, id=portchain_id,
+ fmt=self.fmt),
+ self.serialize(update_data))
+
+ instance.update_port_chain.assert_called_with(
+ mock.ANY, portchain_id,
+ port_chain=update_data)
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_chain', res)
+ self.assertEqual(res['port_chain'], return_value)
+
+ def test_port_chain_update_nonuuid_flow_classifiers(self):
+ portchain_id = _uuid()
+ data = {'port_chain': {
+ 'flow_classifiers': ['nouuid'],
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_CHAIN_PATH, id=portchain_id, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_chain_update_port_pair_groups(self):
+ portchain_id = _uuid()
+ update_data = {'port_chain': {
+ 'port_pair_groups': [_uuid()]
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_CHAIN_PATH, id=portchain_id,
+ fmt=self.fmt),
+ self.serialize(update_data)
+ )
+
+ def test_port_chain_update_chain_parameters(self):
+ portchain_id = _uuid()
+ update_data = {'port_chain': {
+ 'chain_parameters': {}
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_CHAIN_PATH, id=portchain_id,
+ fmt=self.fmt),
+ self.serialize(update_data)
+ )
+
+ def test_port_chain_delete(self):
+ self._test_entity_delete('port_chain')
+
+ def _get_expected_port_pair_group(self, data):
+ return {'port_pair_group': {
+ 'description': data['port_pair_group'].get('description') or '',
+ 'name': data['port_pair_group'].get('name') or '',
+ 'port_pairs': data['port_pair_group'].get('port_pairs') or [],
+ 'tenant_id': data['port_pair_group']['tenant_id']
+ }}
+
+ def test_create_port_pair_group(self):
+ portpairgroup_id = _uuid()
+ data = {'port_pair_group': {
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair_group(data)
+ return_value = copy.copy(expected_data['port_pair_group'])
+ return_value.update({'id': portpairgroup_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair_group.return_value = return_value
+ res = self.api.post(
+ _get_path(PORT_PAIR_GROUP_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair_group.assert_called_with(
+ mock.ANY,
+ port_pair_group=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair_group', res)
+ self.assertEqual(return_value, res['port_pair_group'])
+
+ def test_create_port_pair_group_all_fields(self):
+ portpairgroup_id = _uuid()
+ data = {'port_pair_group': {
+ 'description': 'desc',
+ 'name': 'test1',
+ 'port_pairs': [],
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair_group(data)
+ return_value = copy.copy(expected_data['port_pair_group'])
+ return_value.update({'id': portpairgroup_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair_group.return_value = return_value
+ res = self.api.post(
+ _get_path(PORT_PAIR_GROUP_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair_group.assert_called_with(
+ mock.ANY,
+ port_pair_group=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair_group', res)
+ self.assertEqual(return_value, res['port_pair_group'])
+
+ def test_create_port_pair_group_nonuuid_port_pairs(self):
+ data = {'port_pair_group': {
+ 'port_pairs': ['nouuid'],
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_PAIR_GROUP_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_group_list(self):
+ portpairgroup_id = _uuid()
+ return_value = [{
+ 'tenant_id': _uuid(),
+ 'id': portpairgroup_id
+ }]
+ instance = self.plugin.return_value
+ instance.get_port_pair_groups.return_value = return_value
+
+ res = self.api.get(
+ _get_path(PORT_PAIR_GROUP_PATH, fmt=self.fmt))
+
+ instance.get_port_pair_groups.assert_called_with(
+ mock.ANY,
+ fields=mock.ANY,
+ filters=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair_groups', res)
+ self.assertEqual(res['port_pair_groups'], return_value)
+
+ def test_port_pair_group_get(self):
+ portpairgroup_id = _uuid()
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portpairgroup_id
+ }
+
+ instance = self.plugin.return_value
+ instance.get_port_pair_group.return_value = return_value
+
+ res = self.api.get(_get_path(PORT_PAIR_GROUP_PATH,
+ id=portpairgroup_id, fmt=self.fmt))
+
+ instance.get_port_pair_group.assert_called_with(
+ mock.ANY,
+ portpairgroup_id,
+ fields=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair_group', res)
+ self.assertEqual(return_value, res['port_pair_group'])
+
+ def test_port_pair_group_update(self):
+ portpairgroup_id = _uuid()
+ update_data = {'port_pair_group': {
+ 'name': 'new_name',
+ 'description': 'new_desc',
+ 'port_pairs': [_uuid()]
+ }}
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portpairgroup_id
+ }
+
+ instance = self.plugin.return_value
+ instance.update_port_pair_group.return_value = return_value
+
+ res = self.api.put(
+ _get_path(
+ PORT_PAIR_GROUP_PATH, id=portpairgroup_id,
+ fmt=self.fmt),
+ self.serialize(update_data))
+
+ instance.update_port_pair_group.assert_called_with(
+ mock.ANY, portpairgroup_id,
+ port_pair_group=update_data)
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair_group', res)
+ self.assertEqual(res['port_pair_group'], return_value)
+
+ def test_port_pair_group_update_nonuuid_port_pairs(self):
+ portpairgroup_id = _uuid()
+ data = {'port_pair_group': {
+ 'port_pairs': ['nouuid']
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_PAIR_GROUP_PATH, id=portpairgroup_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_group_delete(self):
+ self._test_entity_delete('port_pair_group')
+
+ def _get_expected_port_pair(self, data):
+ return {'port_pair': {
+ 'name': data['port_pair'].get('name') or '',
+ 'description': data['port_pair'].get('description') or '',
+ 'ingress': data['port_pair']['ingress'],
+ 'egress': data['port_pair']['egress'],
+ 'service_function_parameters': data['port_pair'].get(
+ 'service_function_parameters') or {'correlation': None},
+ 'tenant_id': data['port_pair']['tenant_id']
+ }}
+
+ def test_create_port_pair(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'ingress': _uuid(),
+ 'egress': _uuid(),
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair(data)
+ return_value = copy.copy(expected_data['port_pair'])
+ return_value.update({'id': portpair_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair.return_value = return_value
+ res = self.api.post(_get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair.assert_called_with(
+ mock.ANY,
+ port_pair=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(return_value, res['port_pair'])
+
+ def test_create_port_pair_all_fields(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'description': 'desc',
+ 'name': 'test1',
+ 'ingress': _uuid(),
+ 'egress': _uuid(),
+ 'service_function_parameters': {'correlation': None},
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair(data)
+ return_value = copy.copy(expected_data['port_pair'])
+ return_value.update({'id': portpair_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair.return_value = return_value
+ res = self.api.post(_get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair.assert_called_with(
+ mock.ANY,
+ port_pair=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(return_value, res['port_pair'])
+
+ def test_create_port_pair_non_service_function_parameters(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'ingress': _uuid(),
+ 'egress': _uuid(),
+ 'service_function_parameters': None,
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair(data)
+ return_value = copy.copy(expected_data['port_pair'])
+ return_value.update({'id': portpair_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair.return_value = return_value
+ res = self.api.post(_get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair.assert_called_with(
+ mock.ANY,
+ port_pair=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(return_value, res['port_pair'])
+
+ def test_create_port_pair_empty_service_function_parameters(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'ingress': _uuid(),
+ 'egress': _uuid(),
+ 'service_function_parameters': {},
+ 'tenant_id': _uuid()
+ }}
+ expected_data = self._get_expected_port_pair(data)
+ return_value = copy.copy(expected_data['port_pair'])
+ return_value.update({'id': portpair_id})
+ instance = self.plugin.return_value
+ instance.create_port_pair.return_value = return_value
+ res = self.api.post(_get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+ instance.create_port_pair.assert_called_with(
+ mock.ANY,
+ port_pair=expected_data)
+ self.assertEqual(res.status_int, exc.HTTPCreated.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(return_value, res['port_pair'])
+
+ def test_create_port_pair_invalid_service_function_parameters(self):
+ data = {'port_pair': {
+ 'ingress': _uuid(),
+ 'egress': _uuid(),
+ 'service_function_parameters': {'abc': 'def'},
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_port_pair_nouuid_ingress(self):
+ data = {'port_pair': {
+ 'ingress': 'abc',
+ 'egress': _uuid(),
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_create_port_pair_nouuid_egress(self):
+ data = {'port_pair': {
+ 'egress': 'abc',
+ 'ingress': _uuid(),
+ 'tenant_id': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.post,
+ _get_path(PORT_PAIR_PATH, fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_list(self):
+ portpair_id = _uuid()
+ return_value = [{
+ 'tenant_id': _uuid(),
+ 'id': portpair_id
+ }]
+ instance = self.plugin.return_value
+ instance.get_port_pairs.return_value = return_value
+
+ res = self.api.get(_get_path(PORT_PAIR_PATH, fmt=self.fmt))
+
+ instance.get_port_pairs.assert_called_with(
+ mock.ANY,
+ fields=mock.ANY,
+ filters=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pairs', res)
+ self.assertEqual(res['port_pairs'], return_value)
+
+ def test_port_pair_get(self):
+ portpair_id = _uuid()
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portpair_id
+ }
+
+ instance = self.plugin.return_value
+ instance.get_port_pair.return_value = return_value
+
+ res = self.api.get(_get_path(PORT_PAIR_PATH,
+ id=portpair_id, fmt=self.fmt))
+
+ instance.get_port_pair.assert_called_with(
+ mock.ANY,
+ portpair_id,
+ fields=mock.ANY
+ )
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(return_value, res['port_pair'])
+
+ def test_port_pair_update(self):
+ portpair_id = _uuid()
+ update_data = {'port_pair': {
+ 'name': 'new_name',
+ 'description': 'new_desc'
+ }}
+ return_value = {
+ 'tenant_id': _uuid(),
+ 'id': portpair_id
+ }
+
+ instance = self.plugin.return_value
+ instance.update_port_pair.return_value = return_value
+
+ res = self.api.put(_get_path(PORT_PAIR_PATH, id=portpair_id,
+ fmt=self.fmt),
+ self.serialize(update_data))
+
+ instance.update_port_pair.assert_called_with(
+ mock.ANY, portpair_id,
+ port_pair=update_data)
+ self.assertEqual(res.status_int, exc.HTTPOk.code)
+ res = self.deserialize(res)
+ self.assertIn('port_pair', res)
+ self.assertEqual(res['port_pair'], return_value)
+
+ def test_port_pair_update_service_function_parameters(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'service_function_parameters': None
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_PAIR_PATH, id=portpair_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_update_ingress(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'ingress': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_PAIR_PATH, id=portpair_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_update_egress(self):
+ portpair_id = _uuid()
+ data = {'port_pair': {
+ 'egress': _uuid()
+ }}
+ self.assertRaises(
+ webtest.app.AppError,
+ self.api.put,
+ _get_path(PORT_PAIR_PATH, id=portpair_id,
+ fmt=self.fmt),
+ self.serialize(data),
+ content_type='application/%s' % self.fmt)
+
+ def test_port_pair_delete(self):
+ self._test_entity_delete('port_pair')
diff --git a/networking_sfc/tests/unit/services/__init__.py b/networking_sfc/tests/unit/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/__init__.py
diff --git a/networking_sfc/tests/unit/services/flowclassifier/__init__.py b/networking_sfc/tests/unit/services/flowclassifier/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/flowclassifier/__init__.py
diff --git a/networking_sfc/tests/unit/services/flowclassifier/test_driver_manager.py b/networking_sfc/tests/unit/services/flowclassifier/test_driver_manager.py
new file mode 100644
index 0000000..56dd991
--- /dev/null
+++ b/networking_sfc/tests/unit/services/flowclassifier/test_driver_manager.py
@@ -0,0 +1,158 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 contextlib
+import mock
+import pkg_resources
+import six
+import stevedore
+
+from oslo_config import cfg
+
+from neutron.tests import base
+
+from networking_sfc.services.flowclassifier.common import config as fc_config
+from networking_sfc.services.flowclassifier.common import exceptions as fc_exc
+from networking_sfc.services.flowclassifier import driver_manager as fc_driver
+
+
+class DriverManagerTestCase(base.BaseTestCase):
+ def setUp(self):
+ super(DriverManagerTestCase, self).setUp()
+
+ @contextlib.contextmanager
+ def driver_manager_context(self, drivers):
+ cfg.CONF.register_opts(fc_config.FLOWCLASSIFIER_DRIVER_OPTS,
+ 'flowclassifier')
+ backup_driver_names = cfg.CONF.flowclassifier.drivers
+ driver_names = [
+ driver_name for driver_name in six.iterkeys(drivers)
+ ]
+ cfg.CONF.set_override('drivers', driver_names, 'flowclassifier')
+ iter_entry_points = pkg_resources.iter_entry_points
+ find_entry_points = stevedore.ExtensionManager._find_entry_points
+ pkg_resources.iter_entry_points = mock.Mock()
+ stevedore.ExtensionManager._find_entry_points = mock.Mock()
+ driver_entry_points = []
+ for driver_name in driver_names:
+ driver_class = mock.Mock()
+ ep = mock.Mock()
+ ep.name = driver_name
+ ep.resolve.return_value = driver_class
+ driver_class.return_value = drivers[driver_name]
+ drivers[driver_name].native_bulk_support = True
+ driver_entry_points.append(ep)
+ pkg_resources.iter_entry_points.return_value = driver_entry_points
+ stevedore.ExtensionManager._find_entry_points.return_value = (
+ driver_entry_points
+ )
+ yield fc_driver.FlowClassifierDriverManager()
+ cfg.CONF.set_override('drivers', backup_driver_names, 'flowclassifier')
+ pkg_resources.iter_entry_points = iter_entry_points
+ stevedore.ExtensionManager._find_entry_points = find_entry_points
+
+ def test_initialize_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ manager.initialize()
+ mock_driver1.initialize.assert_called_once_with()
+ mock_driver2.initialize.assert_called_once_with()
+
+ def test_create_flow_classifier_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.create_flow_classifier(mocked_context)
+ mock_driver1.create_flow_classifier.assert_called_once_with(
+ mocked_context)
+ mock_driver2.create_flow_classifier.assert_called_once_with(
+ mocked_context)
+
+ def test_create_flow_classifier_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.create_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ fc_exc.FlowClassifierDriverError,
+ manager.create_flow_classifier, mocked_context
+ )
+
+ def test_update_flow_classifier_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.update_flow_classifier(mocked_context)
+ mock_driver1.update_flow_classifier.assert_called_once_with(
+ mocked_context)
+ mock_driver2.update_flow_classifier.assert_called_once_with(
+ mocked_context)
+
+ def test_update_flow_classifier_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.update_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ fc_exc.FlowClassifierDriverError,
+ manager.update_flow_classifier, mocked_context
+ )
+
+ def test_delete_flow_classifier_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.delete_flow_classifier(mocked_context)
+ mock_driver1.delete_flow_classifier.assert_called_once_with(
+ mocked_context)
+ mock_driver2.delete_flow_classifier.assert_called_once_with(
+ mocked_context)
+
+ def test_delete_flow_classifier_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.delete_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ fc_exc.FlowClassifierDriverError,
+ manager.delete_flow_classifier, mocked_context
+ )
diff --git a/networking_sfc/tests/unit/services/flowclassifier/test_plugin.py b/networking_sfc/tests/unit/services/flowclassifier/test_plugin.py
new file mode 100644
index 0000000..2c814f8
--- /dev/null
+++ b/networking_sfc/tests/unit/services/flowclassifier/test_plugin.py
@@ -0,0 +1,168 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 copy
+import mock
+
+from networking_sfc.services.flowclassifier.common import context as fc_ctx
+from networking_sfc.services.flowclassifier.common import exceptions as fc_exc
+from networking_sfc.tests.unit.db import test_flowclassifier_db
+
+FLOWCLASSIFIER_PLUGIN_KLASS = (
+ "networking_sfc.services.flowclassifier."
+ "plugin.FlowClassifierPlugin"
+)
+
+
+class FlowClassifierPluginTestCase(
+ test_flowclassifier_db.FlowClassifierDbPluginTestCase
+):
+ def setUp(
+ self, core_plugin=None, flowclassifier_plugin=None, ext_mgr=None
+ ):
+ if not flowclassifier_plugin:
+ flowclassifier_plugin = FLOWCLASSIFIER_PLUGIN_KLASS
+ self.driver_manager_p = mock.patch(
+ 'networking_sfc.services.flowclassifier.driver_manager.'
+ 'FlowClassifierDriverManager'
+ )
+ self.fake_driver_manager_class = self.driver_manager_p.start()
+ self.fake_driver_manager = mock.Mock()
+ self.fake_driver_manager_class.return_value = self.fake_driver_manager
+ self.plugin_context = None
+ super(FlowClassifierPluginTestCase, self).setUp(
+ core_plugin=core_plugin,
+ flowclassifier_plugin=flowclassifier_plugin,
+ ext_mgr=ext_mgr
+ )
+
+ def _record_context(self, plugin_context):
+ self.plugin_context = plugin_context
+
+ def test_create_flow_classifier_driver_manager_called(self):
+ self.fake_driver_manager.create_flow_classifier = mock.Mock(
+ side_effect=self._record_context)
+ with self.flow_classifier(flow_classifier={}) as fc:
+ driver_manager = self.fake_driver_manager
+ driver_manager.create_flow_classifier.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, fc_ctx.FlowClassifierContext
+ )
+ self.assertIn('flow_classifier', fc)
+ self.assertEqual(
+ self.plugin_context.current, fc['flow_classifier'])
+
+ def test_create_flow_classifier_driver_manager_exception(self):
+ self.fake_driver_manager.create_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierDriverError(
+ method='create_flow_classifier'
+ )
+ )
+ self._create_flow_classifier(
+ self.fmt, {}, expected_res_status=500)
+ self._test_list_resources('flow_classifier', [])
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_flow_classifier.assert_called_once_with(
+ mock.ANY
+ )
+
+ def test_update_flow_classifier_driver_manager_called(self):
+ self.fake_driver_manager.update_flow_classifier = mock.Mock(
+ side_effect=self._record_context)
+ with self.flow_classifier(flow_classifier={'name': 'test1'}) as fc:
+ req = self.new_update_request(
+ 'flow_classifiers', {'flow_classifier': {'name': 'test2'}},
+ fc['flow_classifier']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ driver_manager = self.fake_driver_manager
+ driver_manager.update_flow_classifier.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, fc_ctx.FlowClassifierContext
+ )
+ self.assertIn('flow_classifier', fc)
+ self.assertIn('flow_classifier', res)
+ self.assertEqual(
+ self.plugin_context.current, res['flow_classifier'])
+ self.assertEqual(
+ self.plugin_context.original, fc['flow_classifier'])
+
+ def test_update_flow_classifier_driver_manager_exception(self):
+ self.fake_driver_manager.update_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierDriverError(
+ method='update_flow_classifier'
+ )
+ )
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }) as fc:
+ self.assertIn('flow_classifier', fc)
+ original_flow_classifier = fc['flow_classifier']
+ req = self.new_update_request(
+ 'flow_classifiers', {'flow_classifier': {'name': 'test2'}},
+ fc['flow_classifier']['id']
+ )
+ updated_flow_classifier = copy.copy(original_flow_classifier)
+ updated_flow_classifier['name'] = 'test2'
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ res = self._list('flow_classifiers')
+ self.assertIn('flow_classifiers', res)
+ self.assertItemsEqual(
+ res['flow_classifiers'], [updated_flow_classifier])
+
+ def test_delete_flow_classifer_driver_manager_called(self):
+ self.fake_driver_manager.delete_flow_classifier = mock.Mock(
+ side_effect=self._record_context)
+ with self.flow_classifier(
+ flow_classifier={}, do_delete=False
+ ) as fc:
+ req = self.new_delete_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_flow_classifier.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, fc_ctx.FlowClassifierContext
+ )
+ self.assertIn('flow_classifier', fc)
+ self.assertEqual(
+ self.plugin_context.current, fc['flow_classifier'])
+
+ def test_delete_flow_classifier_driver_manager_exception(self):
+ self.fake_driver_manager.delete_flow_classifier = mock.Mock(
+ side_effect=fc_exc.FlowClassifierDriverError(
+ method='delete_flow_classifier'
+ )
+ )
+ with self.flow_classifier(flow_classifier={
+ 'name': 'test1'
+ }, do_delete=False) as fc:
+ req = self.new_delete_request(
+ 'flow_classifiers', fc['flow_classifier']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ self._test_list_resources('flow_classifier', [fc])
diff --git a/networking_sfc/tests/unit/services/sfc/__init__.py b/networking_sfc/tests/unit/services/sfc/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/__init__.py
diff --git a/networking_sfc/tests/unit/services/sfc/agent/__init__.py b/networking_sfc/tests/unit/services/sfc/agent/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/agent/__init__.py
diff --git a/networking_sfc/tests/unit/services/sfc/agent/test-agent.py b/networking_sfc/tests/unit/services/sfc/agent/test-agent.py
new file mode 100644
index 0000000..113c343
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/agent/test-agent.py
@@ -0,0 +1,4012 @@
+# Copyright 2015 Huawei. All rights reserved.
+#
+# 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 mock
+import six
+
+from neutron.agent.common import ovs_lib
+from neutron.agent.common import utils
+from neutron.agent import rpc as agent_rpc
+from neutron import context
+from neutron.tests import base
+
+from networking_sfc.services.sfc.agent import (
+ ovs_sfc_agent as agent)
+from networking_sfc.services.sfc.agent import br_int
+from networking_sfc.services.sfc.agent import br_phys
+from networking_sfc.services.sfc.agent import br_tun
+from networking_sfc.services.sfc.common import ovs_ext_lib
+
+
+class OVSSfcAgentTestCase(base.BaseTestCase):
+ def setUp(self):
+ super(OVSSfcAgentTestCase, self).setUp()
+ mock.patch(
+ 'neutron.agent.common.ovs_lib.OVSBridge.get_ports_attributes',
+ return_value=[]
+ ).start()
+ mock.patch(
+ 'neutron.agent.common.ovs_lib.BaseOVS.config',
+ new_callable=mock.PropertyMock,
+ return_value={}
+ ).start()
+ self.executed_cmds = []
+ self.node_flowrules = []
+ self.backup_plugin_rpc = agent.SfcPluginApi
+ self.plugin_rpc = mock.Mock()
+ self.plugin_rpc.get_flowrules_by_host_portid = mock.Mock(
+ side_effect=self.mock_get_flowrules_by_host_portid
+ )
+ self.plugin_rpc.get_all_src_node_flowrules = mock.Mock(
+ side_effect=self.mock_get_all_src_node_flowrules
+ )
+ agent.SfcPluginApi = mock.Mock(
+ return_value=self.plugin_rpc
+ )
+ self.create_consumers = mock.patch.object(
+ agent_rpc, "create_consumers",
+ self.mock_create_consumers
+ )
+ self.create_consumers.start()
+ self.execute = mock.patch.object(
+ utils, "execute", self.mock_execute,
+ spec=utils.execute)
+ self.execute.start()
+ self.added_flows = []
+ self.add_flow = mock.patch.object(
+ ovs_lib.OVSBridge, "add_flow", self.mock_add_flow
+ )
+ self.add_flow.start()
+ self.deleted_flows = []
+ self.delete_flows = mock.patch.object(
+ ovs_lib.OVSBridge, "delete_flows", self.mock_delete_flows
+ )
+ self.delete_flows.start()
+ self.int_patch = 1
+ self.tun_patch = 2
+ self.default_port_mapping = {
+ 'patch-int': {
+ 'ofport': self.int_patch
+ },
+ 'patch-tun': {
+ 'ofport': self.tun_patch
+ }
+ }
+ self.port_mapping = {}
+ self.get_vif_port_by_id = mock.patch.object(
+ ovs_lib.OVSBridge, "get_vif_port_by_id",
+ self.mock_get_vif_port_by_id
+ )
+ self.get_vif_port_by_id.start()
+ self.get_port_ofport = mock.patch.object(
+ ovs_lib.OVSBridge, "get_port_ofport",
+ self.mock_get_port_ofport
+ )
+ self.get_port_ofport.start()
+ self.set_secure_mode = mock.patch.object(
+ ovs_lib.OVSBridge, "set_secure_mode",
+ self.mock_set_secure_mode
+ )
+ self.set_secure_mode.start()
+ self.protocols = []
+ self.set_protocols = mock.patch.object(
+ ovs_lib.OVSBridge, "set_protocols",
+ self.mock_set_protocols
+ )
+ self.set_protocols.start()
+ self.del_controller = mock.patch.object(
+ ovs_lib.OVSBridge, "del_controller",
+ self.mock_del_controller
+ )
+ self.del_controller.start()
+ self.get_bridges = mock.patch.object(
+ ovs_lib.BaseOVS, "get_bridges",
+ self.mock_get_bridges
+ )
+ self.get_bridges.start()
+ self.get_vif_ports = mock.patch.object(
+ ovs_lib.OVSBridge, "get_vif_ports",
+ self.mock_get_vif_ports
+ )
+ self.get_vif_ports.start()
+ self.get_ports_attributes = mock.patch.object(
+ ovs_lib.OVSBridge, "get_ports_attributes",
+ self.mock_get_ports_attributes
+ )
+ self.get_ports_attributes.start()
+ self.delete_port = mock.patch.object(
+ ovs_lib.OVSBridge, "delete_port",
+ self.mock_delete_port
+ )
+ self.delete_port.start()
+ self.create = mock.patch.object(
+ ovs_lib.OVSBridge, "create",
+ self.mock_create
+ )
+ self.create.start()
+ self.add_port = mock.patch.object(
+ ovs_lib.OVSBridge, "add_port",
+ self.mock_add_port
+ )
+ self.add_port.start()
+ self.bridge_exists = mock.patch.object(
+ ovs_lib.BaseOVS, "bridge_exists",
+ self.mock_bridge_exists
+ )
+ self.bridge_exists.start()
+ self.port_exists = mock.patch.object(
+ ovs_lib.BaseOVS, "port_exists",
+ self.mock_port_exists
+ )
+ self.port_exists.start()
+ self.apply_flows = mock.patch.object(
+ ovs_lib.DeferredOVSBridge, "apply_flows",
+ self.mock_apply_flows
+ )
+ self.apply_flows.start()
+ self.group_mapping = {}
+ self.deleted_groups = []
+ self.dump_group_for_id = mock.patch.object(
+ ovs_ext_lib.OVSBridgeExt, "dump_group_for_id",
+ self.mock_dump_group_for_id
+ )
+ self.dump_group_for_id.start()
+ self.add_group = mock.patch.object(
+ ovs_ext_lib.OVSBridgeExt, "add_group",
+ self.mock_add_group
+ )
+ self.add_group.start()
+ self.mod_group = mock.patch.object(
+ ovs_ext_lib.OVSBridgeExt, "mod_group",
+ self.mock_mod_group
+ )
+ self.mod_group.start()
+ self.delete_group = mock.patch.object(
+ ovs_ext_lib.OVSBridgeExt, "delete_group",
+ self.mock_delete_group
+ )
+ self.delete_group.start()
+ self.local_ip = '10.0.0.1'
+ self.bridge_classes = {
+ 'br_int': br_int.OVSIntegrationBridge,
+ 'br_phys': br_phys.OVSPhysicalBridge,
+ 'br_tun': br_tun.OVSTunnelBridge,
+ }
+ self.context = context.get_admin_context_without_session()
+ self.init_agent()
+
+ def init_agent(self):
+ self.added_flows = []
+ self.deleted_flows = []
+ self.group_mapping = {}
+ self.deleted_groups = []
+ self.agent = agent.OVSSfcAgent(
+ self.bridge_classes,
+ 'br-int',
+ 'br-tun',
+ self.local_ip,
+ {},
+ 2,
+ tunnel_types=['gre', 'vxlan']
+ )
+
+ def mock_create_consumers(
+ self, endpoints, prefix, topic_details, start_listening=True
+ ):
+ self.added_flows = []
+ self.deleted_flows = []
+ return mock.Mock()
+
+ def mock_delete_group(self, group_id):
+ if group_id == 'all':
+ self.group_mapping = {}
+ else:
+ if group_id in self.group_mapping:
+ del self.group_mapping[group_id]
+ else:
+ self.deleted_groups.append(group_id)
+
+ def mock_mod_group(self, group_id, **kwargs):
+ kwargs['group_id'] = group_id
+ self.group_mapping[group_id] = kwargs
+
+ def mock_add_group(self, group_id, **kwargs):
+ kwargs['group_id'] = group_id
+ self.group_mapping[group_id] = kwargs
+
+ def mock_dump_group_for_id(self, group_id):
+ if group_id in self.group_mapping:
+ group_list = []
+ group = self.group_mapping[group_id]
+ for group_key, group_value in six.iteritems(group):
+ group_list.append('%s=%s' % (group_key, group_value))
+ return ' '.join(group_list)
+ else:
+ return ''
+
+ def mock_set_secure_mode(self):
+ pass
+
+ def mock_set_protocols(self, protocols):
+ self.protocols = protocols
+
+ def mock_del_controller(self):
+ pass
+
+ def mock_get_bridges(self):
+ return ['br-int', 'br-tun']
+
+ def mock_get_port_ofport(self, port_name):
+ for port_id, port_values in six.iteritems(self.port_mapping):
+ if port_values['port_name'] == port_name:
+ return port_values['ofport']
+ if port_name in self.default_port_mapping:
+ return self.default_port_mapping[port_name]['ofport']
+ return ovs_lib.INVALID_OFPORT
+
+ def mock_add_port(self, port_name, *interface_attr_tuples):
+ return self.mock_get_port_ofport(port_name)
+
+ def mock_bridge_exists(self, bridge_name):
+ return True
+
+ def mock_port_exists(self, port_name):
+ return True
+
+ def mock_apply_flows(self):
+ pass
+
+ def mock_get_vif_port_by_id(self, port_id):
+ if port_id in self.port_mapping:
+ port_values = self.port_mapping[port_id]
+ return ovs_lib.VifPort(
+ port_values['port_name'],
+ port_values['ofport'],
+ port_id,
+ port_values['vif_mac'],
+ self.agent.int_br
+ )
+
+ def mock_get_vif_ports(self):
+ vif_ports = []
+ for port_id, port_values in six.iteritems(self.port_mapping):
+ vif_ports.append(
+ ovs_lib.VifPort(
+ port_values['port_name'],
+ port_values['ofport'],
+ port_id,
+ port_values['vif_mac'],
+ self.agent.int_br
+ )
+ )
+ return vif_ports
+
+ def mock_get_ports_attributes(
+ self, table, columns=None, ports=None,
+ check_error=True, log_errors=True,
+ if_exists=False
+ ):
+ port_infos = []
+ for port_id, port_values in six.iteritems(self.port_mapping):
+ port_info = {}
+ if columns:
+ if 'name' in columns:
+ port_info['name'] = port_values['port_name']
+ if 'ofport' in columns:
+ port_info['ofport'] = port_values['ofport']
+ if 'extenal_ids' in columns:
+ port_info['extenal_ids'] = {
+ 'iface-id': port_id,
+ 'attached-mac': port_values['vif_mac']
+ }
+ if 'other_config' in columns:
+ port_info['other_config'] = {}
+ if 'tag' in columns:
+ port_info['tag'] = []
+ else:
+ port_info = {
+ 'name': port_values['port_name'],
+ 'ofport': port_values['ofport'],
+ 'extenal_ids': {
+ 'iface-id': port_id,
+ 'attached-mac': port_values['vif_mac']
+ },
+ 'other_config': {},
+ 'tag': []
+ }
+ if ports:
+ if port_values['port_name'] in ports:
+ port_infos.append(port_info)
+ else:
+ port_infos.append(port_info)
+ return port_infos
+
+ def mock_delete_port(self, port_name):
+ found_port_id = None
+ for port_id, port_values in six.iteritems(self.port_mapping):
+ if port_values['port_name'] == port_name:
+ found_port_id = port_id
+ if found_port_id:
+ del self.port_mapping[found_port_id]
+
+ def mock_create(self, secure_mode=False):
+ pass
+
+ def mock_add_flow(self, *args, **kwargs):
+ if kwargs not in self.added_flows:
+ self.added_flows.append(kwargs)
+
+ def mock_delete_flows(self, *args, **kwargs):
+ if kwargs not in self.deleted_flows:
+ self.deleted_flows.append(kwargs)
+
+ def mock_get_flowrules_by_host_portid(self, context, port_id):
+ return [
+ flowrule
+ for flowrule in self.node_flowrules
+ if (
+ flowrule['ingress'] == port_id or
+ flowrule['egress'] == port_id
+ )
+ ]
+
+ def mock_get_all_src_node_flowrules(self, context):
+ return [
+ flowrule
+ for flowrule in self.node_flowrules
+ if (
+ flowrule['node_type'] == 'src_node' and
+ flowrule['egress'] is None
+ )
+ ]
+
+ def mock_execute(self, cmd, *args, **kwargs):
+ self.executed_cmds.append(' '.join(cmd))
+
+ def tearDown(self):
+ agent.SfcPluginApi = self.backup_plugin_rpc
+ self.create_consumers.stop()
+ self.execute.stop()
+ self.add_flow.stop()
+ self.delete_flows.stop()
+ self.get_vif_port_by_id.stop()
+ self.get_port_ofport.stop()
+ self.set_secure_mode.stop()
+ self.set_protocols.stop()
+ self.del_controller.stop()
+ self.get_bridges.stop()
+ self.get_vif_ports.stop()
+ self.get_ports_attributes.stop()
+ self.delete_port.stop()
+ self.create.stop()
+ self.add_port.stop()
+ self.bridge_exists.stop()
+ self.port_exists.stop()
+ self.apply_flows.stop()
+ self.dump_group_for_id.stop()
+ self.add_group.stop()
+ self.mod_group.stop()
+ self.delete_group.stop()
+ self.node_flowrules = []
+ self.added_flows = []
+ self.deleted_flows = []
+ self.group_mapping = {}
+ self.deleted_groups = []
+ self.port_mapping = {}
+ super(OVSSfcAgentTestCase, self).tearDown()
+
+ def test_update_empty_flow_rules(self):
+ self.port_mapping = {
+ 'dd7374b9-a6ac-4a66-a4a6-7d3dee2a1579': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '2f1d2140-42ce-4979-9542-7ef25796e536': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,%d)' % agent.SF_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.FWD_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.GRP_SELECTOR,
+ 'in_port': self.int_patch,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': agent.FWD_SELECTOR
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {}
+ )
+
+ def test_update_flow_rules_port_pair(self):
+ self.port_mapping = {
+ 'dd7374b9-a6ac-4a66-a4a6-7d3dee2a1579': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '2f1d2140-42ce-4979-9542-7ef25796e536': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'dd7374b9-a6ac-4a66-a4a6-7d3dee2a1579',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 75,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:fd:b2',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'2f1d2140-42ce-4979-9542-7ef25796e536',
+ 'next_group_id': None,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'84c1411f-7a94-4b4f-9a8b-ad9607c67c76',
+ 'add_fcs': [],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f7'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,%d)' % agent.SF_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.FWD_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.GRP_SELECTOR,
+ 'in_port': self.int_patch,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': agent.SF_SELECTOR
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {}
+ )
+
+ def test_update_flow_rules_flow_classifiers(self):
+ self.port_mapping = {
+ 'e1229670-2a07-450d-bdc9-34e71c301206': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:72:05',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': u'9bedd01e-c216-4dfd-b48e-fbd5c8212ba4',
+ 'next_group_id': 1,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'8cba323e-5e67-4df0-a4b0-7e1ef486a656',
+ 'add_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ 'e1229670-2a07-450d-bdc9-34e71c301206'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 200,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 400,
+ 'logical_source_port': (
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4')
+ }],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f7'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': 'e1229670-2a07-450d-bdc9-34e71c301206',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:c5:f3',
+ 'network_type': 'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': u'test2',
+ 'nsp': 256,
+ 'portchain_id': '8cba323e-5e67-4df0-a4b0-7e1ef486a656',
+ 'add_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': 'tcp',
+ 'logical_destination_port': (
+ 'e1229670-2a07-450d-bdc9-34e71c301206'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 200,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': 'IPv4',
+ 'destination_port_range_max': 400,
+ 'logical_source_port': (
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4')
+ }],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f8'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,%d)' % agent.SF_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.FWD_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.GRP_SELECTOR,
+ 'in_port': self.int_patch,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': agent.SF_SELECTOR
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {}
+ )
+
+ def test_update_flow_rules_flow_classifiers_port_pairs(self):
+ self.port_mapping = {
+ '8768d2b3-746d-4868-ae0e-e81861c2b4e6': {
+ 'port_name': 'port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '29e38fb2-a643-43b1-baa8-a86596461cd5': {
+ 'port_name': 'port2',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '82a575e0-6a6e-46ba-a5fc-692407839a85': {
+ 'port_name': 'port3',
+ 'ofport': 60,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '93466f5d-252e-4552-afc6-5fb3f6019f76': {
+ 'port_name': 'port4',
+ 'ofport': 25,
+ 'vif_mac': '00:01:02:03:06:10'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': '8768d2b3-746d-4868-ae0e-e81861c2b4e6',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:cf:23'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:ed:01',
+ 'network_type': 'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '29e38fb2-a643-43b1-baa8-a86596461cd5',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'add_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': '10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': 'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': '73e97aad-8c0f-44e3-bee0-c0a641b00b66'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': '82a575e0-6a6e-46ba-a5fc-692407839a85',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:a6:84',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'add_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': '10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': 'fa385d84-7d78-44e7-aa8d-7b4a279a14d7'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': '8768d2b3-746d-4868-ae0e-e81861c2b4e6',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': '82a575e0-6a6e-46ba-a5fc-692407839a85',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:a6:84'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:cf:23',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': '93466f5d-252e-4552-afc6-5fb3f6019f76',
+ 'next_group_id': None,
+ 'host_id': 'test3',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'add_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': '10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': '07cc65a8-e99b-4175-a2f1-69b87eb8090a'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,%d)' % agent.SF_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.FWD_SELECTOR,
+ 'dl_type': 34887,
+ 'priority': agent.PC_DEF_PRI
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': 'resubmit(,%d)' % agent.GRP_SELECTOR,
+ 'in_port': self.int_patch,
+ 'priority': agent.PC_DEF_PRI,
+ 'table': agent.FWD_SELECTOR
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 42,
+ 'nw_dst': u'10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': u'10.100.0.0/16',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': agent.GRP_SELECTOR
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:60',
+ 'dl_dst': '00:01:02:03:06:09',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': agent.SF_SELECTOR
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:a6:84,'
+ 'set_field:33->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': u'10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': u'10.100.0.0/16',
+ 'priority': 0,
+ 'table': agent.GRP_SELECTOR,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 25,
+ 'nw_dst': u'10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': u'10.100.0.0/16',
+ 'priority': agent.PC_DEF_PRI,
+ 'table': 0,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': agent.SF_SELECTOR
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:cf:23,'
+ 'set_field:33->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_update_flow_rules_flow_classifiers_multi_port_groups(self):
+ self.port_mapping = {
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef': {
+ 'port_name': 'port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '1ebf82cf-70f9-43fd-8b90-6546f7d13040': {
+ 'port_name': 'port2',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '34032c43-5207-43bb-95cb-cf426266fa11': {
+ 'port_name': 'port3',
+ 'ofport': 60,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ 'eaeec782-4ee8-4c7f-8ecb-f759dab4c723': {
+ 'port_name': 'port4',
+ 'ofport': 25,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ 'f56df7aa-e521-41ce-9001-ed7bedb65c9e': {
+ 'port_name': 'port5',
+ 'ofport': 5,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ '15dc026d-0520-4f92-9841-1056e62fdcaa': {
+ 'port_name': 'port6',
+ 'ofport': 50,
+ 'vif_mac': '00:01:02:03:06:12'
+ },
+ 'd98a48fe-4ef7-4aa6-89fa-02312e54c1bd': {
+ 'port_name': 'port7',
+ 'ofport': 4,
+ 'vif_mac': '00:01:02:03:06:13'
+ },
+ 'd412d042-d8bc-4dd9-b2be-b29c7e8b2c1b': {
+ 'port_name': 'port8',
+ 'ofport': 8,
+ 'vif_mac': '00:01:02:03:06:14'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': '34032c43-5207-43bb-95cb-cf426266fa11',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:b0:88'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 37,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:74:91',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '6331a00d-779b-462b-b0e4-6a65aa3164ef',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'd0b48df7-47ab-4909-b864-9aae1a6ee6fb',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '1ebf82cf-70f9-43fd-8b90-6546f7d13040'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': 'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef')
+ }],
+ 'id': 'bbb1e50c-ecbb-400c-a7e9-8aed8f36993f'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 251,
+ 'ingress': '1ebf82cf-70f9-43fd-8b90-6546f7d13040',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 37,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:b7:0d',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'd0b48df7-47ab-4909-b864-9aae1a6ee6fb',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '1ebf82cf-s70f9-43fd-8b90-6546f7d13040'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef')
+ }],
+ 'id': '7ed75c14-2283-484a-97b8-30e23fbf7457'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': '34032c43-5207-43bb-95cb-cf426266fa11',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'f56df7aa-e521-41ce-9001-ed7bedb65c9e',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:b1:0d'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 37,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:b0:88',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'eaeec782-4ee8-4c7f-8ecb-f759dab4c723',
+ 'next_group_id': 2,
+ 'host_id': u'test3',
+ 'nsp': 256,
+ 'portchain_id': u'd0b48df7-47ab-4909-b864-9aae1a6ee6fb',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '1ebf82cf-70f9-43fd-8b90-6546f7d13040'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef')
+ }],
+ 'id': 'f9fd9c7a-0100-43fb-aea9-30c67f2a731a'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': 'f56df7aa-e521-41ce-9001-ed7bedb65c9e',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': 'd98a48fe-4ef7-4aa6-89fa-02312e54c1bd',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:4e:dd'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 37,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:b1:0d',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'15dc026d-0520-4f92-9841-1056e62fdcaa',
+ 'next_group_id': 3,
+ 'host_id': u'test5',
+ 'nsp': 256,
+ 'portchain_id': u'd0b48df7-47ab-4909-b864-9aae1a6ee6fb',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '1ebf82cf-70f9-43fd-8b90-6546f7d13040'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef')
+ }],
+ 'id': '62f4bb35-1b4a-4cc4-bf07-f40ed5c2d6a7'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 252,
+ 'ingress': u'd98a48fe-4ef7-4aa6-89fa-02312e54c1bd',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'1ebf82cf-70f9-43fd-8b90-6546f7d13040',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:b7:0d'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 37,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:4e:dd',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'd412d042-d8bc-4dd9-b2be-b29c7e8b2c1b',
+ 'next_group_id': None,
+ 'host_id': u'test7',
+ 'nsp': 256,
+ 'portchain_id': u'd0b48df7-47ab-4909-b864-9aae1a6ee6fb',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '1ebf82cf-70f9-43fd-8b90-6546f7d13040'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '6331a00d-779b-462b-b0e4-6a65aa3164ef')
+ }],
+ 'id': 'a535e740-02cc-47ef-aab1-7bcb1594db9b'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': self.int_patch,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 6,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:42'
+ ),
+ 'dl_dst': '00:01:02:03:06:08',
+ 'dl_type': 34887,
+ 'mpls_label': 65788,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 25,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:2',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:60'
+ ),
+ 'dl_dst': '00:01:02:03:06:09',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65789,'
+ 'set_mpls_ttl:253,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 50,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:3',
+ 'dl_type': 34887,
+ 'mpls_label': 65789,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:5'
+ ),
+ 'dl_dst': '00:01:02:03:06:11',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:b7:0d,'
+ 'set_field:37->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65788,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65788,'
+ 'set_mpls_ttl:252,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 8,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:4'
+ ),
+ 'dl_dst': '00:01:02:03:06:13',
+ 'dl_type': 34887,
+ 'mpls_label': 65789,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:b0:88,'
+ 'set_field:37->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ },
+ 2: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:b1:0d,'
+ 'set_field:37->tun_id,output:[]'
+ ),
+ 'group_id': 2,
+ 'type': 'select'
+ },
+ 3: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:4e:dd,'
+ 'set_field:37->tun_id,output:[]'
+ ),
+ 'group_id': 3,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_update_flow_rules_flow_classifiers_multi_port_pairs(self):
+ self.port_mapping = {
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4': {
+ 'port_name': 'port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '21047d09-eaa7-4296-af56-b509e4a10853': {
+ 'port_name': 'port2',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '38266cfe-cd42-413e-80ff-d0d0c74ad260': {
+ 'port_name': 'port3',
+ 'ofport': 60,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '272be90c-b140-4e9d-8dd3-1993fbb3656c': {
+ 'port_name': 'port4',
+ 'ofport': 25,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ 'd1791c8d-a07a-4f35-bd52-b99395da0d76': {
+ 'port_name': 'port5',
+ 'ofport': 5,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ 'ed2804bd-d61a-49e7-9007-76d2540ae78a': {
+ 'port_name': 'port6',
+ 'ofport': 50,
+ 'vif_mac': '00:01:02:03:06:12'
+ },
+ 'bdf4f759-ca35-4cf5-89ac-53da0d6b3fbf': {
+ 'port_name': 'port7',
+ 'ofport': 4,
+ 'vif_mac': '00:01:02:03:06:13'
+ },
+ 'a55b9062-d3fa-4dc2-a4df-bb8b2a908c19': {
+ 'port_name': 'port8',
+ 'ofport': 8,
+ 'vif_mac': '00:01:02:03:06:14'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'38266cfe-cd42-413e-80ff-d0d0c74ad260',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:74:c1'
+ }, {
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'd1791c8d-a07a-4f35-bd52-b99395da0d76',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:4f:6e'
+ }, {
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'bdf4f759-ca35-4cf5-89ac-53da0d6b3fbf',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:d5:66'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 51,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:9c:70',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': u'9864d8e8-0aff-486e-8b84-7a8d20c017d4',
+ 'next_group_id': 1,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'3dddbb0c-5ac4-437c-9b62-ed7ddf8df37f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '21047d09-eaa7-4296-af56-b509e4a10853'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4')
+ }],
+ 'id': '677dfe31-8566-4bd8-8a1e-5f8efd7a45eb'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': u'21047d09-eaa7-4296-af56-b509e4a10853',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 51,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:67:cb',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': u'test2',
+ 'nsp': 256,
+ 'portchain_id': u'3dddbb0c-5ac4-437c-9b62-ed7ddf8df37f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '21047d09-eaa7-4296-af56-b509e4a10853'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4')
+ }],
+ 'id': '4f275568-38cb-45a1-a162-e0d1d4ef335d'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'38266cfe-cd42-413e-80ff-d0d0c74ad260',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'21047d09-eaa7-4296-af56-b509e4a10853',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:67:cb'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 51,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:74:c1',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'272be90c-b140-4e9d-8dd3-1993fbb3656c',
+ 'next_group_id': None,
+ 'host_id': u'test3',
+ 'nsp': 256,
+ 'portchain_id': u'3dddbb0c-5ac4-437c-9b62-ed7ddf8df37f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '21047d09-eaa7-4296-af56-b509e4a10853'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4')
+ }],
+ 'id': '48fd97b1-e166-4aff-906f-8096a48a7cb1'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'd1791c8d-a07a-4f35-bd52-b99395da0d76',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'21047d09-eaa7-4296-af56-b509e4a10853',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:67:cb'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 51,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:4f:6e',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'ed2804bd-d61a-49e7-9007-76d2540ae78a',
+ 'next_group_id': None,
+ 'host_id': u'test5',
+ 'nsp': 256,
+ 'portchain_id': u'3dddbb0c-5ac4-437c-9b62-ed7ddf8df37f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '21047d09-eaa7-4296-af56-b509e4a10853'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4')
+ }],
+ 'id': '48fd97b1-e166-4aff-906f-8096a48a7cb1'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'bdf4f759-ca35-4cf5-89ac-53da0d6b3fbf',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'21047d09-eaa7-4296-af56-b509e4a10853',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:67:cb'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 51,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:d5:66',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'a55b9062-d3fa-4dc2-a4df-bb8b2a908c19',
+ 'next_group_id': None,
+ 'host_id': u'test7',
+ 'nsp': 256,
+ 'portchain_id': u'3dddbb0c-5ac4-437c-9b62-ed7ddf8df37f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '21047d09-eaa7-4296-af56-b509e4a10853'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '9864d8e8-0aff-486e-8b84-7a8d20c017d4')
+ }],
+ 'id': '48fd97b1-e166-4aff-906f-8096a48a7cb1'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': self.int_patch,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 6,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:42'
+ ),
+ 'dl_dst': '00:01:02:03:06:08',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:67:cb,'
+ 'set_field:51->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 25,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:60'
+ ),
+ 'dl_dst': '00:01:02:03:06:09',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 50,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:5'
+ ),
+ 'dl_dst': '00:01:02:03:06:11',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 8,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:4'
+ ),
+ 'dl_dst': '00:01:02:03:06:13',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:74:c1,'
+ 'set_field:51->tun_id,output:[],'
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:4f:6e,'
+ 'set_field:51->tun_id,output:[],'
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:d5:66,'
+ 'set_field:51->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_update_flow_rules_multi_flow_classifiers(self):
+ self.port_mapping = {
+ '54abe601-6685-4c38-9b9d-0d8381a43d56': {
+ 'port_name': 'port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ 'c2de00c2-bd91-4f60-8a7d-5a3ea8f65e77': {
+ 'port_name': 'port2',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '460a5875-b0c6-408e-ada4-0ef01d39bcff': {
+ 'port_name': 'port3',
+ 'ofport': 60,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ 'b2b8a556-593b-4695-8812-cdd33a314867': {
+ 'port_name': 'port4',
+ 'ofport': 25,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ '2656a373-a985-4940-90d1-cfe172951e0c': {
+ 'port_name': 'port5',
+ 'ofport': 5,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ 'a979a847-3014-43ea-b37d-5a3775a173c7': {
+ 'port_name': 'port6',
+ 'ofport': 50,
+ 'vif_mac': '00:01:02:03:06:12'
+ }
+ }
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'2656a373-a985-4940-90d1-cfe172951e0c',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:5f:ea'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 58,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:b9:09',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': u'54abe601-6685-4c38-9b9d-0d8381a43d56',
+ 'next_group_id': 1,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'3eefdf29-ea8f-4794-a36f-5e60ec7fe208',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '460a5875-b0c6-408e-ada4-0ef01d39bcff'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '54abe601-6685-4c38-9b9d-0d8381a43d56')
+ }],
+ 'id': 'd2e675d3-739e-4451-95d5-a15e23c6eaac'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'2656a373-a985-4940-90d1-cfe172951e0c',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:5f:ea'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 58,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:4d:d1',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': u'c2de00c2-bd91-4f60-8a7d-5a3ea8f65e77',
+ 'next_group_id': 1,
+ 'host_id': u'test3',
+ 'nsp': 256,
+ 'portchain_id': u'3eefdf29-ea8f-4794-a36f-5e60ec7fe208',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ 'b2b8a556-593b-4695-8812-cdd33a314867'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'c2de00c2-bd91-4f60-8a7d-5a3ea8f65e77')
+ }],
+ 'id': 'd2e675d3-739e-4451-95d5-a15e23c6eaac'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': u'460a5875-b0c6-408e-ada4-0ef01d39bcff',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 58,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:fc:b8',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': u'test2',
+ 'nsp': 256,
+ 'portchain_id': u'3eefdf29-ea8f-4794-a36f-5e60ec7fe208',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '460a5875-b0c6-408e-ada4-0ef01d39bcff'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '54abe601-6685-4c38-9b9d-0d8381a43d56')
+ }],
+ 'id': '029823ae-8524-4e1c-8f5b-4ee7ec55f1bd'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': u'b2b8a556-593b-4695-8812-cdd33a314867',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 58,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:7b:15',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': u'test4',
+ 'nsp': 256,
+ 'portchain_id': u'3eefdf29-ea8f-4794-a36f-5e60ec7fe208',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ 'b2b8a556-593b-4695-8812-cdd33a314867'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'c2de00c2-bd91-4f60-8a7d-5a3ea8f65e77')
+ }],
+ 'id': '029823ae-8524-4e1c-8f5b-4ee7ec55f1bd'
+ }
+ )
+ self.agent.update_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'2656a373-a985-4940-90d1-cfe172951e0c',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'460a5875-b0c6-408e-ada4-0ef01d39bcff',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:fc:b8'
+ }, {
+ 'local_endpoint': u'10.0.0.2',
+ 'ingress': u'b2b8a556-593b-4695-8812-cdd33a314867',
+ 'weight': 1,
+ 'mac_address': u'12:34:56:78:7b:15'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 58,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:5f:ea',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'a979a847-3014-43ea-b37d-5a3775a173c7',
+ 'next_group_id': None,
+ 'host_id': u'test5',
+ 'nsp': 256,
+ 'portchain_id': u'3eefdf29-ea8f-4794-a36f-5e60ec7fe208',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ '460a5875-b0c6-408e-ada4-0ef01d39bcff'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '54abe601-6685-4c38-9b9d-0d8381a43d56')
+ }, {
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'logical_destination_port': (
+ 'b2b8a556-593b-4695-8812-cdd33a314867'),
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'destination_port_range_min': None,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'c2de00c2-bd91-4f60-8a7d-5a3ea8f65e77')
+ }],
+ 'id': '983cfa51-f9e6-4e36-8f6c-0c84df915cd1'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:%d' % self.int_patch,
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': self.int_patch,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 6,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 42,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:60'
+ ),
+ 'dl_dst': '00:01:02:03:06:09',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:25'
+ ),
+ 'dl_dst': '00:01:02:03:06:10',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:fc:b8,'
+ 'set_field:58->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:%d' % self.tun_patch
+ ),
+ 'dl_type': 2048,
+ 'in_port': 50,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:7b:15,'
+ 'set_field:58->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:5'
+ ),
+ 'dl_dst': '00:01:02:03:06:11',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:5f:ea,'
+ 'set_field:58->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_delete_flow_rules_port_pair(self):
+ self.port_mapping = {
+ 'dd7374b9-a6ac-4a66-a4a6-7d3dee2a1579': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '2f1d2140-42ce-4979-9542-7ef25796e536': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': u'dd7374b9-a6ac-4a66-a4a6-7d3dee2a1579',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 75,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:fd:b2',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': u'2f1d2140-42ce-4979-9542-7ef25796e536',
+ 'next_group_id': None,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'84c1411f-7a94-4b4f-9a8b-ad9607c67c76',
+ 'add_fcs': [],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f7'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.deleted_flows, [{
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': '65791',
+ 'table': 5
+ }, {
+ 'dl_type': 34887,
+ 'mpls_label': '65790',
+ 'table': 31
+ }]
+ )
+ self.assertEqual(
+ self.deleted_groups, [
+ ]
+ )
+
+ def test_delete_flow_rules_flow_classifiers(self):
+ self.port_mapping = {
+ 'e1229670-2a07-450d-bdc9-34e71c301206': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': None,
+ 'add_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': u'12:34:56:78:72:05',
+ 'network_type': u'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': u'9bedd01e-c216-4dfd-b48e-fbd5c8212ba4',
+ 'next_group_id': 1,
+ 'host_id': u'test1',
+ 'nsp': 256,
+ 'portchain_id': u'8cba323e-5e67-4df0-a4b0-7e1ef486a656',
+ 'del_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ 'e1229670-2a07-450d-bdc9-34e71c301206'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4')
+ }],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f7'
+ }
+ )
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': 'e1229670-2a07-450d-bdc9-34e71c301206',
+ 'next_hops': None,
+ 'add_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:c5:f3',
+ 'network_type': 'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': u'test2',
+ 'nsp': 256,
+ 'portchain_id': '8cba323e-5e67-4df0-a4b0-7e1ef486a656',
+ 'del_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': 'tcp',
+ 'logical_destination_port': (
+ 'e1229670-2a07-450d-bdc9-34e71c301206'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': 'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '9bedd01e-c216-4dfd-b48e-fbd5c8212ba4')
+ }],
+ 'id': '611bdc42-12b3-4639-8faf-83da4e6403f8'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.deleted_flows, [{
+ 'dl_type': 2048,
+ 'in_port': 42,
+ 'nw_dst': u'10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': u'10.100.0.0/16',
+ 'table': 0,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'dl_type': 34887,
+ 'mpls_label': '65791',
+ 'table': 31
+ }, {
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': '65790',
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.deleted_groups, [1]
+ )
+
+ def test_delete_flow_rules_flow_classifiers_port_pairs(self):
+ self.port_mapping = {
+ '8768d2b3-746d-4868-ae0e-e81861c2b4e6': {
+ 'port_name': 'port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '29e38fb2-a643-43b1-baa8-a86596461cd5': {
+ 'port_name': 'port2',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '82a575e0-6a6e-46ba-a5fc-692407839a85': {
+ 'port_name': 'port3',
+ 'ofport': 60,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '93466f5d-252e-4552-afc6-5fb3f6019f76': {
+ 'port_name': 'port4',
+ 'ofport': 25,
+ 'vif_mac': '00:01:02:03:06:10'
+ }
+ }
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': '8768d2b3-746d-4868-ae0e-e81861c2b4e6',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:cf:23'
+ }],
+ 'add_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:ed:01',
+ 'network_type': 'gre',
+ 'local_endpoint': u'10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '29e38fb2-a643-43b1-baa8-a86596461cd5',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'del_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': u'10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': '10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': 'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': '73e97aad-8c0f-44e3-bee0-c0a641b00b66'
+ }
+ )
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 253,
+ 'ingress': '82a575e0-6a6e-46ba-a5fc-692407839a85',
+ 'next_hops': None,
+ 'add_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:a6:84',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'del_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': '10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': 'fa385d84-7d78-44e7-aa8d-7b4a279a14d7'
+ }
+ )
+ self.agent.delete_flow_rules(
+ self.context, flowrule_entries={
+ 'nsi': 254,
+ 'ingress': '8768d2b3-746d-4868-ae0e-e81861c2b4e6',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.2',
+ 'ingress': '82a575e0-6a6e-46ba-a5fc-692407839a85',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:a6:84'
+ }],
+ 'add_fcs': [],
+ 'segment_id': 33,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:cf:23',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': '93466f5d-252e-4552-afc6-5fb3f6019f76',
+ 'next_group_id': None,
+ 'host_id': 'test3',
+ 'nsp': 256,
+ 'portchain_id': 'b9570dc9-822b-41fc-a27c-d915a21a3fe8',
+ 'del_fcs': [{
+ 'source_port_range_min': 100,
+ 'destination_ip_prefix': '10.200.0.0/16',
+ 'protocol': u'tcp',
+ 'logical_destination_port': (
+ '82a575e0-6a6e-46ba-a5fc-692407839a85'),
+ 'l7_parameters': {},
+ 'source_port_range_max': 100,
+ 'source_ip_prefix': u'10.100.0.0/16',
+ 'destination_port_range_min': 300,
+ 'ethertype': u'IPv4',
+ 'destination_port_range_max': 300,
+ 'logical_source_port': (
+ '29e38fb2-a643-43b1-baa8-a86596461cd5')
+ }],
+ 'id': '07cc65a8-e99b-4175-a2f1-69b87eb8090a'
+ }
+ )
+ self.assertEqual(
+ self.executed_cmds, [
+ ]
+ )
+ self.assertEqual(
+ self.deleted_flows, [{
+ 'dl_type': 2048,
+ 'in_port': 42,
+ 'nw_dst': u'10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': '10.100.0.0/16',
+ 'table': 0,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'dl_type': 34887,
+ 'mpls_label': '65791',
+ 'table': 31
+ }, {
+ 'dl_dst': '00:01:02:03:06:09',
+ 'dl_type': 34887,
+ 'mpls_label': '65790',
+ 'table': 5
+ }, {
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': '65791',
+ 'table': 5
+ }, {
+ 'dl_type': 2048,
+ 'in_port': 25,
+ 'nw_dst': '10.200.0.0/16',
+ 'nw_proto': 6,
+ 'nw_src': u'10.100.0.0/16',
+ 'table': 0,
+ 'tp_dst': '0x12c/0xffff',
+ 'tp_src': '0x64/0xffff'
+ }, {
+ 'dl_type': 34887,
+ 'mpls_label': '65790',
+ 'table': 31
+ }]
+ )
+ self.assertEqual(
+ self.deleted_groups, [1]
+ )
+
+ def test_init_agent_empty_flowrules(self):
+ self.node_flowrules = []
+ self.init_agent()
+ self.assertItemsEqual(
+ self.added_flows,
+ [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }]
+ )
+ self.assertEqual(self.group_mapping, {})
+
+ def test_init_agent_portchain_portpairs(self):
+ self.port_mapping = {
+ '4f72c5fc-37e9-4e6f-8cd8-e8166c4b45c4': {
+ 'port_name': 'ingress',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '57f35c35-dceb-4934-9a78-b40a0a3e16b3': {
+ 'port_name': 'egress',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 254,
+ 'ingress': '4f72c5fc-37e9-4e6f-8cd8-e8166c4b45c4',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 34,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:2d:f4',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'sf_node',
+ 'egress': '57f35c35-dceb-4934-9a78-b40a0a3e16b3',
+ 'next_group_id': None,
+ 'host_id': u'test2',
+ 'nsp': 256,
+ 'portchain_id': '0f604e43-c941-4f42-a96c-8bd027e5507d',
+ 'add_fcs': [],
+ 'id': 'b6ebb2c3-4e9c-4146-8a74-f3985173dc44'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows,
+ [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': agent.SF_SELECTOR
+ }]
+ )
+ self.assertEqual(self.group_mapping, {})
+
+ def test_init_agent_portchain_flowclassifiers(self):
+ self.port_mapping = {
+ '5aa33c52-535a-48eb-a77c-e02329bb9eb7': {
+ 'port_name': 'src_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '079d214c-1aea-439d-bf3c-dad03db47dcb': {
+ 'port_name': 'dst_port',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 253,
+ 'ingress': '5aa33c52-535a-48eb-a77c-e02329bb9eb7',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:ac:22',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.3',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'd66efb47-f080-41da-8499-c6e89327ecc0',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '5aa33c52-535a-48eb-a77c-e02329bb9eb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '079d214c-1aea-439d-bf3c-dad03db47dcb'
+ }],
+ 'id': '9d8ec269-874a-42b2-825f-d25858341ec2'
+ }, {
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 43,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:e3:b3',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '079d214c-1aea-439d-bf3c-dad03db47dcb',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'd66efb47-f080-41da-8499-c6e89327ecc0',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '5aa33c52-535a-48eb-a77c-e02329bb9eb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '079d214c-1aea-439d-bf3c-dad03db47dcb'
+ }],
+ 'id': u'361811ed-2902-4d35-9fe4-a3a2b062ef37'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows,
+ [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(self.group_mapping, {})
+
+ def test_init_agent_portchain_flow_classifiers_port_pairs(self):
+ self.port_mapping = {
+ '2881f577-3828-40f2-855d-2f86d63a4733': {
+ 'port_name': 'dst_port',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '5546e281-319b-4bdd-95c9-37fe4244aeb3': {
+ 'port_name': 'ingress',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ 'c45ccd73-46ad-4d91-b44d-68c15a822521': {
+ 'port_name': 'egress',
+ 'ofport': 43,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ 'd2ebbafb-500e-4926-9751-de73906a1e00': {
+ 'port_name': 'src_port',
+ 'ofport': 44,
+ 'vif_mac': '00:01:02:03:06:10'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 253,
+ 'ingress': '2881f577-3828-40f2-855d-2f86d63a4733',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 67,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:17:0c',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.3',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'cddb174c-9e50-4411-b844-41ecb9caf4c4',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': u'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2881f577-3828-40f2-855d-2f86d63a4733'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': 'd2ebbafb-500e-4926-9751-de73906a1e00'
+ }],
+ 'id': '752ca419-6729-461f-993f-fbd44bbd0edb'
+ }, {
+ 'nsi': 254,
+ 'ingress': '5546e281-319b-4bdd-95c9-37fe4244aeb3',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': '2881f577-3828-40f2-855d-2f86d63a4733',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:17:0c'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 67,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:ca:de',
+ 'network_type': u'gre',
+ 'local_endpoint': '10.0.0.4',
+ 'node_type': 'sf_node',
+ 'egress': 'c45ccd73-46ad-4d91-b44d-68c15a822521',
+ 'next_group_id': None,
+ 'host_id': 'test4',
+ 'nsp': 256,
+ 'portchain_id': 'cddb174c-9e50-4411-b844-41ecb9caf4c4',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2881f577-3828-40f2-855d-2f86d63a4733'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': 'd2ebbafb-500e-4926-9751-de73906a1e00'
+ }],
+ 'id': 'f70d81ec-1b7c-4ab4-9cf3-da5375ad47e9'
+ }, {
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.4',
+ 'ingress': '5546e281-319b-4bdd-95c9-37fe4244aeb3',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:ca:de'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 67,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:8c:68',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': 'd2ebbafb-500e-4926-9751-de73906a1e00',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'cddb174c-9e50-4411-b844-41ecb9caf4c4',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2881f577-3828-40f2-855d-2f86d63a4733'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': 'd2ebbafb-500e-4926-9751-de73906a1e00'
+ }],
+ 'id': 'f52624f0-81d9-4041-81cf-dfe151d3a949'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,'
+ 'output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 44,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:17:0c,'
+ 'set_field:67->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 43,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:42',
+ 'dl_dst': '00:01:02:03:06:08',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:ca:de,'
+ 'set_field:67->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_init_agent_portchain_multi_port_groups_port_pairs(self):
+ self.port_mapping = {
+ '495d5bcf-f8ef-47d7-995a-5a8ef2e6d1ea': {
+ 'port_name': 'ingress1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '0dd212fb-1e0f-4b1a-abc2-a3a39bbab3ef': {
+ 'port_name': 'egress1',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '6d7aa494-7796-46ea-9cfe-52d2b0f84217': {
+ 'port_name': 'src_port',
+ 'ofport': 43,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '028c5816-7d4b-453e-8ec2-f3a084ae992f': {
+ 'port_name': 'ingress2',
+ 'ofport': 44,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ '3e4e8d33-334b-4c67-8e04-143eeb6f8351': {
+ 'port_name': 'egress2',
+ 'ofport': 45,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ '73d1dbc7-ba46-4b16-85a0-73b106a96fa1': {
+ 'port_name': 'dst_port',
+ 'ofport': 46,
+ 'vif_mac': '00:01:02:03:06:12'
+ },
+ '1778085d-9f81-4e1e-9748-0bafece63344': {
+ 'port_name': 'ingress3',
+ 'ofport': 47,
+ 'vif_mac': '00:01:02:03:06:13'
+ },
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f': {
+ 'port_name': 'egress3',
+ 'ofport': 48,
+ 'vif_mac': '00:01:02:03:06:14'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 254,
+ 'ingress': '495d5bcf-f8ef-47d7-995a-5a8ef2e6d1ea',
+ 'next_hops': [{
+ 'local_endpoint': u'10.0.0.6',
+ 'ingress': '73d1dbc7-ba46-4b16-85a0-73b106a96fa1',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:51:cc'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 7,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:1d:84',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.4',
+ 'node_type': 'sf_node',
+ 'egress': '0dd212fb-1e0f-4b1a-abc2-a3a39bbab3ef',
+ 'next_group_id': 2,
+ 'host_id': 'test3',
+ 'nsp': 256,
+ 'portchain_id': '0aa6b9fe-6b5e-4b72-91aa-45bce6587ca7',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '6d7aa494-7796-46ea-9cfe-52d2b0f84217'
+ }],
+ 'id': u'1fe85cf2-41fb-4b30-80de-4ae35d3c2b1c'
+ }, {
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.4',
+ 'ingress': '495d5bcf-f8ef-47d7-995a-5a8ef2e6d1ea',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:1d:84'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 7,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:45:d7',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '6d7aa494-7796-46ea-9cfe-52d2b0f84217',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': '0aa6b9fe-6b5e-4b72-91aa-45bce6587ca7',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '6d7aa494-7796-46ea-9cfe-52d2b0f84217'
+ }],
+ 'id': '3c4b700b-e993-4378-b41a-95f609b3c799'
+ }, {
+ 'nsi': 252,
+ 'ingress': '028c5816-7d4b-453e-8ec2-f3a084ae992f',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': 'a47cbe65-ea3f-4faa-af27-8212a121c91f',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:54:76'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 7,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:47:34',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.8',
+ 'node_type': 'sf_node',
+ 'egress': '3e4e8d33-334b-4c67-8e04-143eeb6f8351',
+ 'next_group_id': None,
+ 'host_id': 'test8',
+ 'nsp': 256,
+ 'portchain_id': '0aa6b9fe-6b5e-4b72-91aa-45bce6587ca7',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': u'6d7aa494-7796-46ea-9cfe-52d2b0f84217'
+ }],
+ 'id': '05574d93-104e-425f-8a30-640721f2c749'
+ }, {
+ 'nsi': 253,
+ 'ingress': '73d1dbc7-ba46-4b16-85a0-73b106a96fa1',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.8',
+ 'ingress': '028c5816-7d4b-453e-8ec2-f3a084ae992f',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:47:34'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 7,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:51:cc',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.6',
+ 'node_type': 'sf_node',
+ 'egress': '1778085d-9f81-4e1e-9748-0bafece63344',
+ 'next_group_id': 3,
+ 'host_id': 'test5',
+ 'nsp': 256,
+ 'portchain_id': '0aa6b9fe-6b5e-4b72-91aa-45bce6587ca7',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '6d7aa494-7796-46ea-9cfe-52d2b0f84217'
+ }],
+ 'id': u'5038a916-93de-4734-a830-d88c9d65566c'
+ }, {
+ 'nsi': 251,
+ 'ingress': 'a47cbe65-ea3f-4faa-af27-8212a121c91f',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 7,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:54:76',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.3',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': '0aa6b9fe-6b5e-4b72-91aa-45bce6587ca7',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'a47cbe65-ea3f-4faa-af27-8212a121c91f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '6d7aa494-7796-46ea-9cfe-52d2b0f84217'
+ }],
+ 'id': '42b8abe6-5bfa-47c5-a992-771e333dae52'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65789,'
+ 'set_mpls_ttl:253,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 47,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:3',
+ 'dl_type': 34887,
+ 'mpls_label': 65789,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'pop_mpls:0x0800,'
+ 'output:46'
+ ),
+ 'dl_dst': '00:01:02:03:06:12',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:54:76,'
+ 'set_field:7->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65788,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65788,'
+ 'set_mpls_ttl:252,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 45,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:44',
+ 'dl_dst': '00:01:02:03:06:10',
+ 'dl_type': 34887,
+ 'mpls_label': 65789,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 43,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 42,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:2',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'dl_dst': '00:01:02:03:05:07',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:48',
+ 'dl_dst': '00:01:02:03:06:14',
+ 'dl_type': 34887,
+ 'mpls_label': 65788,
+ 'priority': 1,
+ 'table': 5
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:1d:84,'
+ 'set_field:7->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ },
+ 2: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:51:cc,'
+ 'set_field:7->tun_id,output:[]'
+ ),
+ 'group_id': 2,
+ 'type': 'select'
+ },
+ 3: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:47:34,'
+ 'set_field:7->tun_id,output:[]'
+ ),
+ 'group_id': 3,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_init_agent_portchain_port_group_multi_port_pairs(self):
+ self.port_mapping = {
+ '8849af69-117d-4db9-83fa-85329b0efbd6': {
+ 'port_name': 'ingress1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '51f58f0f-6870-4e75-9fd1-13cf3ce29b3e': {
+ 'port_name': 'egress1',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ 'a57a8160-a202-477b-aca1-e7c006bc93a2': {
+ 'port_name': 'src_port',
+ 'ofport': 43,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '23d02749-7f2b-456d-b9f1-7869300375d4': {
+ 'port_name': 'ingress2',
+ 'ofport': 44,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ 'c5dacf1c-f84a-43e0-8873-b2cba77970af': {
+ 'port_name': 'egress2',
+ 'ofport': 45,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7': {
+ 'port_name': 'dst_port',
+ 'ofport': 46,
+ 'vif_mac': '00:01:02:03:06:12'
+ },
+ 'b299c792-28c8-4f6a-84a0-589163a9b1d4': {
+ 'port_name': 'ingress3',
+ 'ofport': 47,
+ 'vif_mac': '00:01:02:03:06:13'
+ },
+ '60d47d04-42c0-4478-9136-6247fd5d058d': {
+ 'port_name': 'egress3',
+ 'ofport': 48,
+ 'vif_mac': '00:01:02:03:06:14'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 254,
+ 'ingress': '8849af69-117d-4db9-83fa-85329b0efbd6',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': '2b17abfa-7afb-4e83-8e15-ad21a6044bb7',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:68:3a'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 68,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:fe:38',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.6',
+ 'node_type': 'sf_node',
+ 'egress': '51f58f0f-6870-4e75-9fd1-13cf3ce29b3e',
+ 'next_group_id': None,
+ 'host_id': 'test6',
+ 'nsp': 256,
+ 'portchain_id': '10f6a764-6963-4b8e-9ae4-a1e5e805915e',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': 'a57a8160-a202-477b-aca1-e7c006bc93a2'
+ }],
+ 'id': u'1409e7b8-ed6f-41ae-ba6b-8ef96bbb8da9'
+ }, {
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.4',
+ 'ingress': 'b299c792-28c8-4f6a-84a0-589163a9b1d4',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:58:ee'
+ }, {
+ 'local_endpoint': '10.0.0.6',
+ 'ingress': '8849af69-117d-4db9-83fa-85329b0efbd6',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:fe:38'
+ }, {
+ 'local_endpoint': '10.0.0.8',
+ 'ingress': '23d02749-7f2b-456d-b9f1-7869300375d4',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:32:30'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 68,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:e0:a9',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': 'a57a8160-a202-477b-aca1-e7c006bc93a2',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': '10f6a764-6963-4b8e-9ae4-a1e5e805915e',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'a57a8160-a202-477b-aca1-e7c006bc93a2')
+ }],
+ 'id': '6c686bd6-a064-4650-ace7-0bd34fa4238a'
+ }, {
+ 'nsi': 254,
+ 'ingress': '23d02749-7f2b-456d-b9f1-7869300375d4',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': '2b17abfa-7afb-4e83-8e15-ad21a6044bb7',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:68:3a'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 68,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:32:30',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.8',
+ 'node_type': 'sf_node',
+ 'egress': 'c5dacf1c-f84a-43e0-8873-b2cba77970af',
+ 'next_group_id': None,
+ 'host_id': u'test8',
+ 'nsp': 256,
+ 'portchain_id': '10f6a764-6963-4b8e-9ae4-a1e5e805915e',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'a57a8160-a202-477b-aca1-e7c006bc93a2')
+ }],
+ 'id': u'1409e7b8-ed6f-41ae-ba6b-8ef96bbb8da9'
+ }, {
+ 'nsi': 253,
+ 'ingress': '2b17abfa-7afb-4e83-8e15-ad21a6044bb7',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 68,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:68:3a',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.3',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': '10f6a764-6963-4b8e-9ae4-a1e5e805915e',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ 'a57a8160-a202-477b-aca1-e7c006bc93a2')
+ }],
+ 'id': '12a279c1-cf81-4c1b-bac3-e9690465aeaf'
+ }, {
+ 'nsi': 254,
+ 'ingress': 'b299c792-28c8-4f6a-84a0-589163a9b1d4',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': '2b17abfa-7afb-4e83-8e15-ad21a6044bb7',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:68:3a'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 68,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:58:ee',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.4',
+ 'node_type': 'sf_node',
+ 'egress': '60d47d04-42c0-4478-9136-6247fd5d058d',
+ 'next_group_id': None,
+ 'host_id': 'test4',
+ 'nsp': 256,
+ 'portchain_id': '10f6a764-6963-4b8e-9ae4-a1e5e805915e',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b17abfa-7afb-4e83-8e15-ad21a6044bb7'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': 'a57a8160-a202-477b-aca1-e7c006bc93a2'
+ }],
+ 'id': '1409e7b8-ed6f-41ae-ba6b-8ef96bbb8da9'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows, [{
+ 'priority': 10,
+ 'table': 0,
+ 'dl_type': 34887,
+ 'actions': 'resubmit(,5)'
+ }, {
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'actions': 'resubmit(,30)'
+ }, {
+ 'priority': 0,
+ 'table': 30,
+ 'actions': 'output:1'
+ }, {
+ 'priority': 10,
+ 'table': 30,
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1
+ }, {
+ 'dl_type': 34887,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:68:3a,'
+ 'set_field:68->tun_id,output:[]'
+ ),
+ 'priority': 0,
+ 'mpls_label': 65790,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0',
+ 'table': 31,
+ 'nw_src': '0.0.0.0/0.0.0.0'
+ }, {
+ 'dl_type': 2048,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'priority': 10,
+ 'tp_dst': '0/0x0',
+ 'table': 0,
+ 'tp_src': '0/0x0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'in_port': 42
+ }, {
+ 'dl_type': 34887,
+ 'actions': 'pop_mpls:0x0800,output:6',
+ 'priority': 1,
+ 'mpls_label': 65791,
+ 'table': 5,
+ 'dl_dst': '00:01:02:03:05:07'
+ }, {
+ 'dl_type': 2048,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'priority': 10,
+ 'tp_dst': '0/0x0',
+ 'table': 0,
+ 'tp_src': '0/0x0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'in_port': 45
+ }, {
+ 'dl_type': 34887,
+ 'actions': 'pop_mpls:0x0800,output:44',
+ 'priority': 1,
+ 'mpls_label': 65791,
+ 'table': 5,
+ 'dl_dst': '00:01:02:03:06:10'
+ }, {
+ 'dl_type': 2048,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'priority': 10,
+ 'tp_dst': '0/0x0',
+ 'table': 0,
+ 'tp_src': '0/0x0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'in_port': 48
+ }, {
+ 'dl_type': 34887,
+ 'actions': 'pop_mpls:0x0800,output:47',
+ 'priority': 1,
+ 'mpls_label': 65791,
+ 'table': 5,
+ 'dl_dst': '00:01:02:03:06:13'
+ }, {
+ 'dl_type': 2048,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:2'
+ ),
+ 'priority': 10,
+ 'tp_dst': '0/0x0',
+ 'table': 0,
+ 'tp_src': '0/0x0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'in_port': 43
+ }, {
+ 'priority': 0,
+ 'table': 31,
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'actions': 'group:1'
+ }, {
+ 'dl_type': 34887,
+ 'actions': 'pop_mpls:0x0800,output:46',
+ 'priority': 1,
+ 'mpls_label': 65790,
+ 'table': 5,
+ 'dl_dst': '00:01:02:03:06:12'
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:58:ee,'
+ 'set_field:68->tun_id,output:[],'
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:fe:38,'
+ 'set_field:68->tun_id,output:[],'
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:32:30,'
+ 'set_field:68->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
+
+ def test_init_agent_portchain_multi_flow_classifiers_port_pairs(self):
+ self.port_mapping = {
+ '7b718ad7-c2cc-4de0-9ac0-d5f4b6e975aa': {
+ 'port_name': 'src_port1',
+ 'ofport': 6,
+ 'vif_mac': '00:01:02:03:05:07'
+ },
+ '9ac01d29-797a-4904-97a0-eecc7661b2ad': {
+ 'port_name': 'ingress',
+ 'ofport': 42,
+ 'vif_mac': '00:01:02:03:06:08'
+ },
+ '02ebda8f-44e5-41ee-8d80-ec47b3c2732e': {
+ 'port_name': 'egress',
+ 'ofport': 43,
+ 'vif_mac': '00:01:02:03:06:09'
+ },
+ '32971131-e44c-4aad-85f9-7d9f10d07393': {
+ 'port_name': 'src_port2',
+ 'ofport': 44,
+ 'vif_mac': '00:01:02:03:06:10'
+ },
+ 'b7c69625-9cde-48dd-8858-5d773b002e73': {
+ 'port_name': 'dst_port1',
+ 'ofport': 45,
+ 'vif_mac': '00:01:02:03:06:11'
+ },
+ '2b7e8e42-b35d-4d49-8397-62088efe144f': {
+ 'port_name': 'dst_port2',
+ 'ofport': 46,
+ 'vif_mac': '00:01:02:03:06:12'
+ }
+ }
+ self.node_flowrules = [{
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.6',
+ 'ingress': '9ac01d29-797a-4904-97a0-eecc7661b2ad',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:52:39'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 82,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:65:d7',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.4',
+ 'node_type': 'src_node',
+ 'egress': '7b718ad7-c2cc-4de0-9ac0-d5f4b6e975aa',
+ 'next_group_id': 1,
+ 'host_id': 'test3',
+ 'nsp': 256,
+ 'portchain_id': 'd92114e8-56df-4bd7-9cf2-fce5ac01c94f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b7e8e42-b35d-4d49-8397-62088efe144f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '7b718ad7-c2cc-4de0-9ac0-d5f4b6e975aa'
+ }],
+ 'id': u'44c469bf-6c48-4f8f-bb4f-de87b44b02b6'
+ }, {
+ 'nsi': 254,
+ 'ingress': '9ac01d29-797a-4904-97a0-eecc7661b2ad',
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.3',
+ 'ingress': 'b7c69625-9cde-48dd-8858-5d773b002e73',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:36:e9'
+ }, {
+ 'local_endpoint': '10.0.0.5',
+ 'ingress': '2b7e8e42-b35d-4d49-8397-62088efe144f',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:51:9a'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 82,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:52:39',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.6',
+ 'node_type': 'sf_node',
+ 'egress': '02ebda8f-44e5-41ee-8d80-ec47b3c2732e',
+ 'next_group_id': None,
+ 'host_id': 'test6',
+ 'nsp': 256,
+ 'portchain_id': 'd92114e8-56df-4bd7-9cf2-fce5ac01c94f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'b7c69625-9cde-48dd-8858-5d773b002e73'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '32971131-e44c-4aad-85f9-7d9f10d07393'
+ }, {
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b7e8e42-b35d-4d49-8397-62088efe144f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': '7b718ad7-c2cc-4de0-9ac0-d5f4b6e975aa'
+ }],
+ 'id': u'c065e0c3-a904-4bac-adf2-f038b717c9c2'
+ }, {
+ 'nsi': 255,
+ 'ingress': None,
+ 'next_hops': [{
+ 'local_endpoint': '10.0.0.6',
+ 'ingress': '9ac01d29-797a-4904-97a0-eecc7661b2ad',
+ 'weight': 1,
+ 'mac_address': '12:34:56:78:52:39'
+ }],
+ 'del_fcs': [],
+ 'segment_id': 82,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:41:cf',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.2',
+ 'node_type': 'src_node',
+ 'egress': '32971131-e44c-4aad-85f9-7d9f10d07393',
+ 'next_group_id': 1,
+ 'host_id': 'test1',
+ 'nsp': 256,
+ 'portchain_id': 'd92114e8-56df-4bd7-9cf2-fce5ac01c94f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'b7c69625-9cde-48dd-8858-5d773b002e73'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '32971131-e44c-4aad-85f9-7d9f10d07393')
+ }],
+ 'id': u'44c469bf-6c48-4f8f-bb4f-de87b44b02b6'
+ }, {
+ 'nsi': 253,
+ 'ingress': 'b7c69625-9cde-48dd-8858-5d773b002e73',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 82,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:36:e9',
+ 'network_type': 'gre',
+ 'local_endpoint': '10.0.0.3',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test2',
+ 'nsp': 256,
+ 'portchain_id': 'd92114e8-56df-4bd7-9cf2-fce5ac01c94f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ 'b7c69625-9cde-48dd-8858-5d773b002e73'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '32971131-e44c-4aad-85f9-7d9f10d07393')
+ }],
+ 'id': '4a61e567-4210-41d9-af82-e01b9da47230'
+ }, {
+ 'nsi': 253,
+ 'ingress': '2b7e8e42-b35d-4d49-8397-62088efe144f',
+ 'next_hops': None,
+ 'del_fcs': [],
+ 'segment_id': 82,
+ 'group_refcnt': 1,
+ 'mac_address': '12:34:56:78:51:9a',
+ 'network_type': 'gre',
+ 'local_endpoint': u'10.0.0.5',
+ 'node_type': 'dst_node',
+ 'egress': None,
+ 'next_group_id': None,
+ 'host_id': 'test4',
+ 'nsp': 256,
+ 'portchain_id': 'd92114e8-56df-4bd7-9cf2-fce5ac01c94f',
+ 'add_fcs': [{
+ 'source_port_range_min': None,
+ 'destination_ip_prefix': None,
+ 'protocol': None,
+ 'ethertype': 'IPv4',
+ 'l7_parameters': {},
+ 'source_port_range_max': None,
+ 'source_ip_prefix': None,
+ 'logical_destination_port': (
+ '2b7e8e42-b35d-4d49-8397-62088efe144f'),
+ 'destination_port_range_min': None,
+ 'destination_port_range_max': None,
+ 'logical_source_port': (
+ '7b718ad7-c2cc-4de0-9ac0-d5f4b6e975aa')
+ }],
+ 'id': '4a61e567-4210-41d9-af82-e01b9da47230'
+ }]
+ self.init_agent()
+ for port_id in self.port_mapping:
+ self.agent.sfc_treat_devices_added_updated(port_id)
+ self.assertItemsEqual(
+ self.added_flows, [{
+ 'actions': 'resubmit(,5)',
+ 'dl_type': 34887,
+ 'priority': 10,
+ 'table': 0
+ }, {
+ 'actions': 'resubmit(,30)',
+ 'dl_type': 34887,
+ 'priority': 10
+ }, {
+ 'actions': 'output:1',
+ 'priority': 0,
+ 'table': 30
+ }, {
+ 'actions': 'resubmit(,31)',
+ 'in_port': 1,
+ 'priority': 10,
+ 'table': 30
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 44,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'group:1',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 0,
+ 'table': 31
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:45',
+ 'dl_dst': '00:01:02:03:06:11',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:36:e9,'
+ 'set_field:82->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65790,'
+ 'set_mpls_ttl:254,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 43,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': (
+ 'mod_dl_dst:12:34:56:78:51:9a,'
+ 'set_field:82->tun_id,output:[]'
+ ),
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 0,
+ 'table': 31,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:42',
+ 'dl_dst': '00:01:02:03:06:08',
+ 'dl_type': 34887,
+ 'mpls_label': 65791,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': 'pop_mpls:0x0800,output:46',
+ 'dl_dst': '00:01:02:03:06:12',
+ 'dl_type': 34887,
+ 'mpls_label': 65790,
+ 'priority': 1,
+ 'table': 5
+ }, {
+ 'actions': (
+ 'push_mpls:0x8847,'
+ 'set_mpls_label:65791,'
+ 'set_mpls_ttl:255,output:2'
+ ),
+ 'dl_type': 2048,
+ 'in_port': 6,
+ 'nw_dst': '0.0.0.0/0.0.0.0',
+ 'nw_src': '0.0.0.0/0.0.0.0',
+ 'priority': 10,
+ 'table': 0,
+ 'tp_dst': '0/0x0',
+ 'tp_src': '0/0x0'
+ }]
+ )
+ self.assertEqual(
+ self.group_mapping, {
+ 1: {
+ 'buckets': (
+ 'bucket=weight=1,'
+ 'mod_dl_dst:12:34:56:78:52:39,'
+ 'set_field:82->tun_id,output:[]'
+ ),
+ 'group_id': 1,
+ 'type': 'select'
+ }
+ }
+ )
diff --git a/networking_sfc/tests/unit/services/sfc/common/__init__.py b/networking_sfc/tests/unit/services/sfc/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/common/__init__.py
diff --git a/networking_sfc/tests/unit/services/sfc/common/test_ovs_ext_lib.py b/networking_sfc/tests/unit/services/sfc/common/test_ovs_ext_lib.py
new file mode 100644
index 0000000..19e20cc
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/common/test_ovs_ext_lib.py
@@ -0,0 +1,93 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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.
+
+from neutron.common import exceptions
+from neutron.tests import base
+
+from networking_sfc.services.sfc.common import ovs_ext_lib
+
+
+class GetPortMaskTestCase(base.BaseTestCase):
+ def setUp(self):
+ super(GetPortMaskTestCase, self).setUp()
+
+ def tearDown(self):
+ super(GetPortMaskTestCase, self).tearDown()
+
+ def test_single_port(self):
+ masks = ovs_ext_lib.get_port_mask(100, 100)
+ self.assertEqual(masks, ['0x64/0xffff'])
+
+ def test_invalid_min_port(self):
+ self.assertRaises(
+ exceptions.InvalidInput,
+ ovs_ext_lib.get_port_mask,
+ 0, 100
+ )
+
+ def test_invalid_max_port(self):
+ self.assertRaises(
+ exceptions.InvalidInput,
+ ovs_ext_lib.get_port_mask,
+ 100, 65536
+ )
+
+ def test_invalid_port_range(self):
+ self.assertRaises(
+ exceptions.InvalidInput,
+ ovs_ext_lib.get_port_mask,
+ 100, 99
+ )
+
+ def test_one_port_mask(self):
+ masks = ovs_ext_lib.get_port_mask(100, 101)
+ self.assertEqual(masks, ['0x64/0xfffe'])
+ masks = ovs_ext_lib.get_port_mask(100, 103)
+ self.assertEqual(masks, ['0x64/0xfffc'])
+ masks = ovs_ext_lib.get_port_mask(32768, 65535)
+ self.assertEqual(masks, ['0x8000/0x8000'])
+
+ def test_multi_port_masks(self):
+ masks = ovs_ext_lib.get_port_mask(101, 102)
+ self.assertEqual(masks, ['0x65/0xffff', '0x66/0xffff'])
+ masks = ovs_ext_lib.get_port_mask(101, 104)
+ self.assertEqual(
+ masks,
+ ['0x65/0xffff', '0x66/0xfffe', '0x68/0xffff']
+ )
+ masks = ovs_ext_lib.get_port_mask(1, 65535)
+ self.assertEqual(
+ masks, [
+ '0x1/0xffff',
+ '0x2/0xfffe',
+ '0x4/0xfffc',
+ '0x8/0xfff8',
+ '0x10/0xfff0',
+ '0x20/0xffe0',
+ '0x40/0xffc0',
+ '0x80/0xff80',
+ '0x100/0xff00',
+ '0x200/0xfe00',
+ '0x400/0xfc00',
+ '0x800/0xf800',
+ '0x1000/0xf000',
+ '0x2000/0xe000',
+ '0x4000/0xc000',
+ '0x8000/0x8000'
+ ]
+ )
+ masks = ovs_ext_lib.get_port_mask(32767, 65535)
+ self.assertEqual(
+ masks, ['0x7fff/0xffff', '0x8000/0x8000']
+ )
diff --git a/networking_sfc/tests/unit/services/sfc/drivers/__init__.py b/networking_sfc/tests/unit/services/sfc/drivers/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/drivers/__init__.py
diff --git a/networking_sfc/tests/unit/services/sfc/drivers/ovs/__init__.py b/networking_sfc/tests/unit/services/sfc/drivers/ovs/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/drivers/ovs/__init__.py
diff --git a/networking_sfc/tests/unit/services/sfc/test_driver_manager.py b/networking_sfc/tests/unit/services/sfc/test_driver_manager.py
new file mode 100644
index 0000000..c247bf2
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/test_driver_manager.py
@@ -0,0 +1,325 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 contextlib
+import mock
+import pkg_resources
+import six
+import stevedore
+
+from oslo_config import cfg
+
+from neutron.tests import base
+
+from networking_sfc.services.sfc.common import config as sfc_config
+from networking_sfc.services.sfc.common import exceptions as sfc_exc
+from networking_sfc.services.sfc import driver_manager as sfc_driver
+
+
+class DriverManagerTestCase(base.BaseTestCase):
+ def setUp(self):
+ super(DriverManagerTestCase, self).setUp()
+
+ @contextlib.contextmanager
+ def driver_manager_context(self, drivers):
+ cfg.CONF.register_opts(sfc_config.SFC_DRIVER_OPTS, 'sfc')
+ backup_driver_names = cfg.CONF.sfc.drivers
+ driver_names = [
+ driver_name for driver_name in six.iterkeys(drivers)
+ ]
+ cfg.CONF.set_override('drivers', driver_names, 'sfc')
+ iter_entry_points = pkg_resources.iter_entry_points
+ find_entry_points = stevedore.ExtensionManager._find_entry_points
+ pkg_resources.iter_entry_points = mock.Mock()
+ stevedore.ExtensionManager._find_entry_points = mock.Mock()
+ driver_entry_points = []
+ for driver_name in driver_names:
+ driver_class = mock.Mock()
+ ep = mock.Mock()
+ ep.name = driver_name
+ ep.resolve.return_value = driver_class
+ driver_class.return_value = drivers[driver_name]
+ drivers[driver_name].native_bulk_support = True
+ driver_entry_points.append(ep)
+ pkg_resources.iter_entry_points.return_value = driver_entry_points
+ stevedore.ExtensionManager._find_entry_points.return_value = (
+ driver_entry_points
+ )
+ yield sfc_driver.SfcDriverManager()
+ cfg.CONF.set_override('drivers', backup_driver_names, 'sfc')
+ pkg_resources.iter_entry_points = iter_entry_points
+ stevedore.ExtensionManager._find_entry_points = find_entry_points
+
+ def test_initialize_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ manager.initialize()
+ mock_driver1.initialize.assert_called_once_with()
+ mock_driver2.initialize.assert_called_once_with()
+
+ def test_create_port_chain_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.create_port_chain(mocked_context)
+ mock_driver1.create_port_chain.assert_called_once_with(
+ mocked_context)
+ mock_driver2.create_port_chain.assert_called_once_with(
+ mocked_context)
+
+ def test_create_port_chain_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.create_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.create_port_chain, mocked_context
+ )
+
+ def test_update_port_chain_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.update_port_chain(mocked_context)
+ mock_driver1.update_port_chain.assert_called_once_with(
+ mocked_context)
+ mock_driver2.update_port_chain.assert_called_once_with(
+ mocked_context)
+
+ def test_update_port_chain_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.update_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.update_port_chain, mocked_context
+ )
+
+ def test_delete_port_chain_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.delete_port_chain(mocked_context)
+ mock_driver1.delete_port_chain.assert_called_once_with(
+ mocked_context)
+ mock_driver2.delete_port_chain.assert_called_once_with(
+ mocked_context)
+
+ def test_delete_port_chain_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.delete_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.delete_port_chain, mocked_context
+ )
+
+ def test_create_port_pair_group_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.create_port_pair_group(mocked_context)
+ mock_driver1.create_port_pair_group.assert_called_once_with(
+ mocked_context)
+ mock_driver2.create_port_pair_group.assert_called_once_with(
+ mocked_context)
+
+ def test_create_port_pair_group_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.create_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.create_port_pair_group, mocked_context
+ )
+
+ def test_update_port_pair_group_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.update_port_pair_group(mocked_context)
+ mock_driver1.update_port_pair_group.assert_called_once_with(
+ mocked_context)
+ mock_driver2.update_port_pair_group.assert_called_once_with(
+ mocked_context)
+
+ def test_update_port_pair_group_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.update_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.update_port_pair_group, mocked_context
+ )
+
+ def test_delete_port_pair_group_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.delete_port_pair_group(mocked_context)
+ mock_driver1.delete_port_pair_group.assert_called_once_with(
+ mocked_context)
+ mock_driver2.delete_port_pair_group.assert_called_once_with(
+ mocked_context)
+
+ def test_delete_port_pair_group_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.delete_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.delete_port_pair_group, mocked_context
+ )
+
+ def test_create_port_pair_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.create_port_pair(mocked_context)
+ mock_driver1.create_port_pair.assert_called_once_with(
+ mocked_context)
+ mock_driver2.create_port_pair.assert_called_once_with(
+ mocked_context)
+
+ def test_create_port_pair_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.create_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.create_port_pair, mocked_context
+ )
+
+ def test_update_port_pair_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.update_port_pair(mocked_context)
+ mock_driver1.update_port_pair.assert_called_once_with(
+ mocked_context)
+ mock_driver2.update_port_pair.assert_called_once_with(
+ mocked_context)
+
+ def test_update_port_pair_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.update_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.update_port_pair, mocked_context
+ )
+
+ def test_delete_port_pair_called(self):
+ mock_driver1 = mock.Mock()
+ mock_driver2 = mock.Mock()
+ with self.driver_manager_context({
+ 'dummy1': mock_driver1,
+ 'dummy2': mock_driver2
+ }) as manager:
+ mocked_context = mock.Mock()
+ manager.delete_port_pair(mocked_context)
+ mock_driver1.delete_port_pair.assert_called_once_with(
+ mocked_context)
+ mock_driver2.delete_port_pair.assert_called_once_with(
+ mocked_context)
+
+ def test_delete_port_pair_exception(self):
+ mock_driver = mock.Mock()
+ mock_driver.delete_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcException
+ )
+ with self.driver_manager_context({
+ 'dummy': mock_driver,
+ }) as manager:
+ mocked_context = mock.Mock()
+ self.assertRaises(
+ sfc_exc.SfcDriverError,
+ manager.delete_port_pair, mocked_context
+ )
diff --git a/networking_sfc/tests/unit/services/sfc/test_plugin.py b/networking_sfc/tests/unit/services/sfc/test_plugin.py
new file mode 100644
index 0000000..d265838
--- /dev/null
+++ b/networking_sfc/tests/unit/services/sfc/test_plugin.py
@@ -0,0 +1,468 @@
+# Copyright 2015 Futurewei. All rights reserved.
+#
+# 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 copy
+import mock
+
+from networking_sfc.services.sfc.common import context as sfc_ctx
+from networking_sfc.services.sfc.common import exceptions as sfc_exc
+from networking_sfc.tests.unit.db import test_sfc_db
+
+SFC_PLUGIN_KLASS = (
+ "networking_sfc.services.sfc.plugin.SfcPlugin"
+)
+
+
+class SfcPluginTestCase(test_sfc_db.SfcDbPluginTestCase):
+ def setUp(self, core_plugin=None, sfc_plugin=None, ext_mgr=None):
+ if not sfc_plugin:
+ sfc_plugin = SFC_PLUGIN_KLASS
+ self.driver_manager_p = mock.patch(
+ 'networking_sfc.services.sfc.driver_manager.SfcDriverManager'
+ )
+ self.fake_driver_manager_class = self.driver_manager_p.start()
+ self.fake_driver_manager = mock.Mock()
+ self.fake_driver_manager_class.return_value = self.fake_driver_manager
+ self.plugin_context = None
+ super(SfcPluginTestCase, self).setUp(
+ core_plugin=core_plugin, sfc_plugin=sfc_plugin,
+ ext_mgr=ext_mgr
+ )
+
+ def _record_context(self, plugin_context):
+ self.plugin_context = plugin_context
+
+ def test_create_port_chain_driver_manager_called(self):
+ self.fake_driver_manager.create_port_chain = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={}) as pg:
+ with self.port_chain(port_chain={
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }) as pc:
+ driver_manager = self.fake_driver_manager
+ driver_manager.create_port_chain.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortChainContext
+ )
+ self.assertIn('port_chain', pc)
+ self.assertEqual(
+ self.plugin_context.current, pc['port_chain'])
+
+ def test_create_port_chain_driver_manager_exception(self):
+ self.fake_driver_manager.create_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='create_port_chain'
+ )
+ )
+ with self.port_pair_group(port_pair_group={}) as pg:
+ self._create_port_chain(
+ self.fmt,
+ {'port_pair_groups': [pg['port_pair_group']['id']]},
+ expected_res_status=500)
+ self._test_list_resources('port_chain', [])
+ self.fake_driver_manager.delete_port_chain.assert_called_once_with(
+ mock.ANY
+ )
+
+ def test_update_port_chain_driver_manager_called(self):
+ self.fake_driver_manager.update_port_chain = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={}) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }) as pc:
+ req = self.new_update_request(
+ 'port_chains', {'port_chain': {'name': 'test2'}},
+ pc['port_chain']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ driver_manager = self.fake_driver_manager
+ driver_manager.update_port_chain.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortChainContext
+ )
+ self.assertIn('port_chain', pc)
+ self.assertIn('port_chain', res)
+ self.assertEqual(
+ self.plugin_context.current, res['port_chain'])
+ self.assertEqual(
+ self.plugin_context.original, pc['port_chain'])
+
+ def test_update_port_chain_driver_manager_exception(self):
+ self.fake_driver_manager.update_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='update_port_chain'
+ )
+ )
+ with self.port_pair_group(port_pair_group={}) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }) as pc:
+ self.assertIn('port_chain', pc)
+ original_port_chain = pc['port_chain']
+ req = self.new_update_request(
+ 'port_chains', {'port_chain': {'name': 'test2'}},
+ pc['port_chain']['id']
+ )
+ updated_port_chain = copy.copy(original_port_chain)
+ updated_port_chain['name'] = 'test2'
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ res = self._list('port_chains')
+ self.assertIn('port_chains', res)
+ self.assertItemsEqual(
+ res['port_chains'], [updated_port_chain])
+
+ def test_delete_port_chain_manager_called(self):
+ self.fake_driver_manager.delete_port_chain = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={}) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_port_chain.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortChainContext
+ )
+ self.assertIn('port_chain', pc)
+ self.assertEqual(self.plugin_context.current, pc['port_chain'])
+
+ def test_delete_port_chain_driver_manager_exception(self):
+ self.fake_driver_manager.delete_port_chain = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='delete_port_chain'
+ )
+ )
+ with self.port_pair_group(port_pair_group={
+ }, do_delete=False) as pg:
+ with self.port_chain(port_chain={
+ 'name': 'test1',
+ 'port_pair_groups': [pg['port_pair_group']['id']]
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_chains', pc['port_chain']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ self._test_list_resources('port_chain', [pc])
+
+ def test_create_port_pair_group_driver_manager_called(self):
+ self.fake_driver_manager.create_port_pair_group = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={}) as pc:
+ fake_driver_manager = self.fake_driver_manager
+ fake_driver_manager.create_port_pair_group.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairGroupContext
+ )
+ self.assertIn('port_pair_group', pc)
+ self.assertEqual(
+ self.plugin_context.current, pc['port_pair_group'])
+
+ def test_create_port_pair_group_driver_manager_exception(self):
+ self.fake_driver_manager.create_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='create_port_pair_group'
+ )
+ )
+ self._create_port_pair_group(self.fmt, {}, expected_res_status=500)
+ self._test_list_resources('port_pair_group', [])
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_port_pair_group.assert_called_once_with(
+ mock.ANY
+ )
+
+ def test_update_port_pair_group_driver_manager_called(self):
+ self.fake_driver_manager.update_port_pair_group = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc:
+ req = self.new_update_request(
+ 'port_pair_groups', {'port_pair_group': {'name': 'test2'}},
+ pc['port_pair_group']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ driver_manager = self.fake_driver_manager
+ driver_manager.update_port_pair_group.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairGroupContext
+ )
+ self.assertIn('port_pair_group', pc)
+ self.assertIn('port_pair_group', res)
+ self.assertEqual(
+ self.plugin_context.current, res['port_pair_group'])
+ self.assertEqual(
+ self.plugin_context.original, pc['port_pair_group'])
+
+ def test_update_port_pair_group_driver_manager_exception(self):
+ self.fake_driver_manager.update_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='update_port_pair_group'
+ )
+ )
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }) as pc:
+ self.assertIn('port_pair_group', pc)
+ original_port_pair_group = pc['port_pair_group']
+ req = self.new_update_request(
+ 'port_pair_groups', {'port_pair_group': {'name': 'test2'}},
+ pc['port_pair_group']['id']
+ )
+ updated_port_pair_group = copy.copy(original_port_pair_group)
+ updated_port_pair_group['name'] = 'test2'
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ res = self._list('port_pair_groups')
+ self.assertIn('port_pair_groups', res)
+ self.assertItemsEqual(
+ res['port_pair_groups'], [updated_port_pair_group])
+
+ def test_delete_port_pair_group_manager_called(self):
+ self.fake_driver_manager.delete_port_pair_group = mock.Mock(
+ side_effect=self._record_context)
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pair_groups', pc['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_port_pair_group.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairGroupContext
+ )
+ self.assertIn('port_pair_group', pc)
+ self.assertEqual(
+ self.plugin_context.current, pc['port_pair_group'])
+
+ def test_delete_port_pair_group_driver_manager_exception(self):
+ self.fake_driver_manager.delete_port_pair_group = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='delete_port_pair_group'
+ )
+ )
+ with self.port_pair_group(port_pair_group={
+ 'name': 'test1'
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pair_groups', pc['port_pair_group']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ self._test_list_resources('port_pair_group', [pc])
+
+ def test_create_port_pair_driver_manager_called(self):
+ self.fake_driver_manager.create_port_pair = mock.Mock(
+ side_effect=self._record_context)
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ driver_manager = self.fake_driver_manager
+ driver_manager.create_port_pair.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairContext
+ )
+ self.assertIn('port_pair', pc)
+ self.assertEqual(self.plugin_context.current, pc['port_pair'])
+
+ def test_create_port_pair_driver_manager_exception(self):
+ self.fake_driver_manager.create_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='create_port_pair'
+ )
+ )
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ self._create_port_pair(
+ self.fmt,
+ {
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ },
+ expected_res_status=500)
+ self._test_list_resources('port_pair', [])
+ driver_manager = self.fake_driver_manager
+ driver_manager.delete_port_pair.assert_called_once_with(
+ mock.ANY
+ )
+
+ def test_update_port_pair_driver_manager_called(self):
+ self.fake_driver_manager.update_port_pair = mock.Mock(
+ side_effect=self._record_context)
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': {'name': 'test2'}},
+ pc['port_pair']['id']
+ )
+ res = self.deserialize(
+ self.fmt,
+ req.get_response(self.ext_api)
+ )
+ driver_manager = self.fake_driver_manager
+ driver_manager.update_port_pair.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairContext
+ )
+ self.assertIn('port_pair', pc)
+ self.assertIn('port_pair', res)
+ self.assertEqual(
+ self.plugin_context.current, res['port_pair'])
+ self.assertEqual(
+ self.plugin_context.original, pc['port_pair'])
+
+ def test_update_port_pair_driver_manager_exception(self):
+ self.fake_driver_manager.update_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='update_port_pair'
+ )
+ )
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }) as pc:
+ self.assertIn('port_pair', pc)
+ original_port_pair = pc['port_pair']
+ req = self.new_update_request(
+ 'port_pairs', {'port_pair': {'name': 'test2'}},
+ pc['port_pair']['id']
+ )
+ updated_port_pair = copy.copy(original_port_pair)
+ updated_port_pair['name'] = 'test2'
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ res = self._list('port_pairs')
+ self.assertIn('port_pairs', res)
+ self.assertItemsEqual(res['port_pairs'], [updated_port_pair])
+
+ def test_delete_port_pair_manager_called(self):
+ self.fake_driver_manager.delete_port_pair = mock.Mock(
+ side_effect=self._record_context)
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 204)
+ fake_driver_manager = self.fake_driver_manager
+ fake_driver_manager.delete_port_pair.assert_called_once_with(
+ mock.ANY
+ )
+ self.assertIsInstance(
+ self.plugin_context, sfc_ctx.PortPairContext
+ )
+ self.assertIn('port_pair', pc)
+ self.assertEqual(self.plugin_context.current, pc['port_pair'])
+
+ def test_delete_port_pair_driver_manager_exception(self):
+ self.fake_driver_manager.delete_port_pair = mock.Mock(
+ side_effect=sfc_exc.SfcDriverError(
+ method='delete_port_pair'
+ )
+ )
+ with self.port(
+ name='port1',
+ device_id='default'
+ ) as src_port, self.port(
+ name='port2',
+ device_id='default'
+ ) as dst_port:
+ with self.port_pair(port_pair={
+ 'name': 'test1',
+ 'ingress': src_port['port']['id'],
+ 'egress': dst_port['port']['id']
+ }, do_delete=False) as pc:
+ req = self.new_delete_request(
+ 'port_pairs', pc['port_pair']['id']
+ )
+ res = req.get_response(self.ext_api)
+ self.assertEqual(res.status_int, 500)
+ self._test_list_resources('port_pair', [pc])