# Copyright (c) 2012 OpenStack Foundation. # # 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 os import fixtures import mock from neutron.common.test_lib import test_config from neutron.common import topics from neutron import context from neutron.extensions import portbindings from neutron import manager from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.db import api as ndb from neutron.plugins.nec import nec_plugin from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit.nec import fake_ofc_manager from neutron.tests.unit import test_db_plugin as test_plugin from neutron.tests.unit import test_security_groups_rpc as test_sg_rpc PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2' NEC_PLUGIN_INI = """ [DEFAULT] api_extensions_path = neutron/plugins/nec/extensions [OFC] driver = neutron.tests.unit.nec.stub_ofc_driver.StubOFCDriver enable_packet_filter = False """ class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase): _plugin_name = PLUGIN_NAME _nec_ini = NEC_PLUGIN_INI def _set_nec_ini(self): self.nec_ini_file = self.useFixture(fixtures.TempDir()).join("nec.ini") with open(self.nec_ini_file, 'w') as f: f.write(self._nec_ini) if 'config_files' in test_config.keys(): for c in test_config['config_files']: if c.rfind("/nec.ini") > -1: test_config['config_files'].remove(c) test_config['config_files'].append(self.nec_ini_file) else: test_config['config_files'] = [self.nec_ini_file] def _clean_nec_ini(self): test_config['config_files'].remove(self.nec_ini_file) os.remove(self.nec_ini_file) self.nec_ini_file = None def rpcapi_update_ports(self, agent_id='nec-q-agent.fake', datapath_id="0xabc", added=[], removed=[]): kwargs = {'topic': topics.AGENT, 'agent_id': agent_id, 'datapath_id': datapath_id, 'port_added': added, 'port_removed': removed} self.callback_nec.update_ports(self.context, **kwargs) def setUp(self): self.addCleanup(mock.patch.stopall) self._set_nec_ini() super(NecPluginV2TestCase, self).setUp(self._plugin_name) # NOTE: `test_config' is global, and most tests don't set # test_config['config_files'] but read this in setUp(). # So clean test_config['config_files'] ASAP, to avoid side effects # on other tests which are running at the same time. self._clean_nec_ini() self.plugin = manager.NeutronManager.get_plugin() self.plugin.ofc = fake_ofc_manager.patch_ofc_manager() self.ofc = self.plugin.ofc self.callback_nec = nec_plugin.NECPluginV2RPCCallbacks(self.plugin) self.context = context.get_admin_context() class TestNecBasicGet(test_plugin.TestBasicGet, NecPluginV2TestCase): pass class TestNecV2HTTPResponse(test_plugin.TestV2HTTPResponse, NecPluginV2TestCase): pass class TestNecPortsV2(test_plugin.TestPortsV2, NecPluginV2TestCase): VIF_TYPE = portbindings.VIF_TYPE_OVS HAS_PORT_FILTER = True class TestNecNetworksV2(test_plugin.TestNetworksV2, NecPluginV2TestCase): pass class TestNecPortBinding(test_bindings.PortBindingsTestCase, NecPluginV2TestCase): VIF_TYPE = portbindings.VIF_TYPE_OVS HAS_PORT_FILTER = True FIREWALL_DRIVER = test_sg_rpc.FIREWALL_HYBRID_DRIVER def setUp(self): test_sg_rpc.set_firewall_driver(self.FIREWALL_DRIVER) super(TestNecPortBinding, self).setUp() class TestNecPortBindingNoSG(TestNecPortBinding): HAS_PORT_FILTER = False FIREWALL_DRIVER = test_sg_rpc.FIREWALL_NOOP_DRIVER class TestNecPortsV2Callback(NecPluginV2TestCase): def _get_portinfo(self, port_id): return ndb.get_portinfo(self.context.session, port_id) def test_portinfo_create(self): with self.port() as port: port_id = port['port']['id'] sport = self.plugin.get_port(self.context, port_id) self.assertEqual(sport['status'], 'DOWN') self.assertEqual(self.ofc.create_ofc_port.call_count, 0) self.assertIsNone(self._get_portinfo(port_id)) portinfo = {'id': port_id, 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) sport = self.plugin.get_port(self.context, port_id) self.assertEqual(sport['status'], 'ACTIVE') self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertIsNotNone(self._get_portinfo(port_id)) expected = [ mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.create_ofc_port(mock.ANY, port_id, mock.ANY), ] self.ofc.assert_has_calls(expected) def test_portinfo_delete_before_port_deletion(self): self._test_portinfo_delete() def test_portinfo_delete_after_port_deletion(self): self._test_portinfo_delete(portinfo_delete_first=False) def _test_portinfo_delete(self, portinfo_delete_first=True): with self.port() as port: port_id = port['port']['id'] portinfo = {'id': port_id, 'port_no': 456} self.assertEqual(self.ofc.create_ofc_port.call_count, 0) self.assertIsNone(self._get_portinfo(port_id)) self.rpcapi_update_ports(added=[portinfo]) self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 0) self.assertIsNotNone(self._get_portinfo(port_id)) # Before port-deletion, switch port removed message is sent. if portinfo_delete_first: self.rpcapi_update_ports(removed=[port_id]) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertIsNone(self._get_portinfo(port_id)) # The port is expected to delete when exiting with-clause. if not portinfo_delete_first: self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertIsNotNone(self._get_portinfo(port_id)) self.rpcapi_update_ports(removed=[port_id]) # Ensure port deletion is called once. self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertIsNone(self._get_portinfo(port_id)) expected = [ mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.create_ofc_port(mock.ANY, port_id, mock.ANY), mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.delete_ofc_port(mock.ANY, port_id, mock.ANY), ] self.ofc.assert_has_calls(expected) def test_portinfo_added_unknown_port(self): portinfo = {'id': 'dummy-p1', 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) self.assertIsNotNone(ndb.get_portinfo(self.context.session, 'dummy-p1')) self.assertEqual(self.ofc.exists_ofc_port.call_count, 0) self.assertEqual(self.ofc.create_ofc_port.call_count, 0) def _test_portinfo_change(self, portinfo_change_first=True): with self.port() as port: port_id = port['port']['id'] self.assertEqual(self.ofc.create_ofc_port.call_count, 0) portinfo = {'id': port_id, 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 0) self.assertEqual(ndb.get_portinfo(self.context.session, port_id).port_no, 123) if portinfo_change_first: portinfo = {'id': port_id, 'port_no': 456} self.rpcapi_update_ports(added=[portinfo]) # OFC port is recreated. self.assertEqual(self.ofc.create_ofc_port.call_count, 2) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertEqual(ndb.get_portinfo(self.context.session, port_id).port_no, 456) if not portinfo_change_first: # The port is expected to delete when exiting with-clause. self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) portinfo = {'id': port_id, 'port_no': 456} self.rpcapi_update_ports(added=[portinfo]) # No OFC operations are expected. self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) self.assertEqual(ndb.get_portinfo(self.context.session, port_id).port_no, 456) def test_portinfo_change(self): self._test_portinfo_change() def test_portinfo_change_for_nonexisting_port(self): self._test_portinfo_change(portinfo_change_first=False) def test_port_migration(self): agent_id_a, datapath_id_a, port_no_a = 'nec-q-agent.aa', '0xaaa', 10 agent_id_b, datapath_id_b, port_no_b = 'nec-q-agent.bb', '0xbbb', 11 with self.port() as port: port_id = port['port']['id'] sport = self.plugin.get_port(self.context, port_id) self.assertEqual(sport['status'], 'DOWN') portinfo_a = {'id': port_id, 'port_no': port_no_a} self.rpcapi_update_ports(agent_id=agent_id_a, datapath_id=datapath_id_a, added=[portinfo_a]) portinfo_b = {'id': port_id, 'port_no': port_no_b} self.rpcapi_update_ports(agent_id=agent_id_b, datapath_id=datapath_id_b, added=[portinfo_b]) self.rpcapi_update_ports(agent_id=agent_id_a, datapath_id=datapath_id_a, removed=[port_id]) sport = self.plugin.get_port(self.context, port_id) self.assertEqual(sport['status'], 'ACTIVE') self.assertTrue(self.ofc.ofc_ports[port_id]) expected = [ mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.create_ofc_port(mock.ANY, port_id, mock.ANY), mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.delete_ofc_port(mock.ANY, port_id, mock.ANY), mock.call.exists_ofc_port(mock.ANY, port_id), mock.call.create_ofc_port(mock.ANY, port_id, mock.ANY), ] self.ofc.assert_has_calls(expected) self.assertEqual(2, self.ofc.create_ofc_port.call_count) self.assertEqual(1, self.ofc.delete_ofc_port.call_count) class TestNecPluginDbTest(NecPluginV2TestCase): def test_update_resource(self): with self.network() as network: self.assertEqual("ACTIVE", network['network']['status']) net_id = network['network']['id'] for status in ["DOWN", "BUILD", "ERROR", "ACTIVE"]: self.plugin._update_resource_status( self.context, 'network', net_id, getattr(nec_plugin.OperationalStatus, status)) n = self.plugin._get_network(self.context, net_id) self.assertEqual(status, n.status) class TestNecPluginOfcManager(NecPluginV2TestCase): def setUp(self): super(TestNecPluginOfcManager, self).setUp() self.ofc = self.plugin.ofc def _create_resource(self, resource, data): collection = resource + 's' data = {resource: data} req = self.new_create_request(collection, data) res = self.deserialize(self.fmt, req.get_response(self.api)) return res[resource] def _update_resource(self, resource, id, data): collection = resource + 's' data = {resource: data} req = self.new_update_request(collection, data, id) res = self.deserialize(self.fmt, req.get_response(self.api)) return res[resource] def _show_resource(self, resource, id): collection = resource + 's' req = self.new_show_request(collection, id) res = self.deserialize(self.fmt, req.get_response(self.api)) return res[resource] def _list_resource(self, resource): collection = resource + 's' req = self.new_list_request(collection) res = req.get_response(self.api) return res[collection] def _delete_resource(self, resource, id): collection = resource + 's' req = self.new_delete_request(collection, id) res = req.get_response(self.api) return res.status_int def test_create_network(self): net = None ctx = mock.ANY with self.network() as network: net = network['network'] self.assertEqual(network['network']['status'], 'ACTIVE') expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_create_network_with_admin_state_down(self): net = None ctx = mock.ANY with self.network(admin_state_up=False) as network: net = network['network'] expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_create_two_network(self): nets = [] ctx = mock.ANY with self.network() as net1: nets.append(net1['network']) self.assertEqual(net1['network']['status'], 'ACTIVE') with self.network() as net2: nets.append(net2['network']) self.assertEqual(net2['network']['status'], 'ACTIVE') expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, nets[0]['id'], nets[0]['name']), mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, nets[1]['id'], nets[1]['name']), mock.call.delete_ofc_network(ctx, nets[1]['id'], mock.ANY), mock.call.delete_ofc_network(ctx, nets[0]['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_create_network_fail(self): self.ofc.create_ofc_network.side_effect = nexc.OFCException( reason='hoge') net = None ctx = mock.ANY # NOTE: We don't delete network through api, but db will be cleaned in # tearDown(). When OFCManager has failed to create a network on OFC, # it does not keeps ofc_network entry and will fail to delete this # network from OFC. Deletion of network is not the scope of this test. with self.network(do_delete=False) as network: net = network['network'] net_ref = self._show('networks', net['id']) self.assertEqual(net_ref['network']['status'], 'ERROR') expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']) ] self.ofc.assert_has_calls(expected) def test_update_network(self): net = None ctx = mock.ANY with self.network() as network: net = network['network'] self.assertEqual(network['network']['status'], 'ACTIVE') # Set admin_state_up to False res = self._update_resource('network', net['id'], {'admin_state_up': False}) self.assertFalse(res['admin_state_up']) # Set admin_state_up to True res = self._update_resource('network', net['id'], {'admin_state_up': True}) self.assertTrue(res['admin_state_up']) expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_create_port_no_ofc_creation(self): net = None p1 = None ctx = mock.ANY with self.subnet() as subnet: with self.port(subnet=subnet) as port: p1 = port['port'] net_id = port['port']['network_id'] net = self._show_resource('network', net_id) self.assertEqual(net['status'], 'ACTIVE') self.assertEqual(p1['status'], 'ACTIVE') expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_create_port_with_ofc_creation(self): net = None p1 = None ctx = mock.ANY with self.subnet() as subnet: with self.port(subnet=subnet) as port: p1 = port['port'] net_id = port['port']['network_id'] net = self._show_resource('network', net_id) self.assertEqual(net['status'], 'ACTIVE') self.assertEqual(p1['status'], 'ACTIVE') # Check the port is not created on OFC self.assertFalse(self.ofc.create_ofc_port.call_count) # Register portinfo, then the port is created on OFC portinfo = {'id': p1['id'], 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) self.assertEqual(self.ofc.create_ofc_port.call_count, 1) expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.create_ofc_port(ctx, p1['id'], mock.ANY), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_delete_network_with_dhcp_port(self): ctx = mock.ANY with self.network() as network: with self.subnet(network=network): net = network['network'] p = self._create_resource('port', {'network_id': net['id'], 'tenant_id': net['tenant_id'], 'device_owner': 'network:dhcp', 'device_id': 'dhcp-port1'}) # Make sure that the port is created on OFC. portinfo = {'id': p['id'], 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) # In a case of dhcp port, the port is deleted automatically # when delete_network. expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.exists_ofc_port(ctx, p['id']), mock.call.create_ofc_port(ctx, p['id'], mock.ANY), mock.call.exists_ofc_port(ctx, p['id']), mock.call.delete_ofc_port(ctx, p['id'], mock.ANY), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected) def test_update_port(self): self._test_update_port_with_admin_state(resource='port') def test_update_network_with_ofc_port(self): self._test_update_port_with_admin_state(resource='network') def _test_update_port_with_admin_state(self, resource='port'): net = None p1 = None ctx = mock.ANY if resource == 'network': net_ini_admin_state = False port_ini_admin_state = True else: net_ini_admin_state = True port_ini_admin_state = False with self.network(admin_state_up=net_ini_admin_state) as network: with self.subnet(network=network) as subnet: with self.port(subnet=subnet, admin_state_up=port_ini_admin_state) as port: p1 = port['port'] net_id = port['port']['network_id'] res_id = net_id if resource == 'network' else p1['id'] net = self._show_resource('network', net_id) # Check the port is not created on OFC self.assertFalse(self.ofc.create_ofc_port.call_count) # Register portinfo, then the port is created on OFC portinfo = {'id': p1['id'], 'port_no': 123} self.rpcapi_update_ports(added=[portinfo]) self.assertFalse(self.ofc.create_ofc_port.call_count) self._update_resource(resource, res_id, {'admin_state_up': True}) self.assertEqual(self.ofc.create_ofc_port.call_count, 1) self.assertFalse(self.ofc.delete_ofc_port.call_count) self._update_resource(resource, res_id, {'admin_state_up': False}) self.assertEqual(self.ofc.delete_ofc_port.call_count, 1) expected = [ mock.call.exists_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_tenant(ctx, self._tenant_id), mock.call.create_ofc_network(ctx, self._tenant_id, net['id'], net['name']), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.create_ofc_port(ctx, p1['id'], mock.ANY), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY), mock.call.exists_ofc_port(ctx, p1['id']), mock.call.delete_ofc_network(ctx, net['id'], mock.ANY), mock.call.delete_ofc_tenant(ctx, self._tenant_id) ] self.ofc.assert_has_calls(expected)