diff options
Diffstat (limited to 'networking_sfc/tests/unit')
25 files changed, 9417 insertions, 0 deletions
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]) |