From 3cc8e78956ee0d1132d723d1513466778268b13c Mon Sep 17 00:00:00 2001 From: Arvind Somya Date: Thu, 29 Aug 2013 13:23:52 -0400 Subject: [PATCH] Cisco plugin portbinding extension support This commit adds portbinding extension support to the Cisco plugin. Change-Id: I87554607860b040b693edeecc2706ca8edbe49b6 Fixes: Bug #1218033 --- .../plugins/cisco/models/virt_phy_sw_v2.py | 87 ++++++++++--------- .../tests/unit/cisco/test_network_plugin.py | 39 ++++----- 2 files changed, 64 insertions(+), 62 deletions(-) diff --git a/neutron/plugins/cisco/models/virt_phy_sw_v2.py b/neutron/plugins/cisco/models/virt_phy_sw_v2.py index aea40f79e..3e8fa2d64 100644 --- a/neutron/plugins/cisco/models/virt_phy_sw_v2.py +++ b/neutron/plugins/cisco/models/virt_phy_sw_v2.py @@ -23,11 +23,11 @@ import inspect import logging import sys -from novaclient.v1_1 import client as nova_client from oslo.config import cfg from neutron.api.v2 import attributes from neutron.db import api as db_api +from neutron.extensions import portbindings from neutron.extensions import providernet as provider from neutron import neutron_plugin_base_v2 from neutron.openstack.common import importutils @@ -51,7 +51,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): """ MANAGE_STATE = True __native_bulk_support = True - supported_extension_aliases = ["provider"] + supported_extension_aliases = ["provider", "binding"] _plugins = {} _methods_to_delegate = ['create_network_bulk', 'get_network', 'get_networks', @@ -176,21 +176,6 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): raise cexc.NetworkSegmentIDNotFound(net_id=network_id) return binding_seg_id.segmentation_id - def _get_instance_host(self, tenant_id, instance_id): - keystone_conf = cfg.CONF.keystone_authtoken - keystone_auth_url = '%s://%s:%s/v2.0/' % (keystone_conf.auth_protocol, - keystone_conf.auth_host, - keystone_conf.auth_port) - nc = nova_client.Client(keystone_conf.admin_user, - keystone_conf.admin_password, - keystone_conf.admin_tenant_name, - keystone_auth_url, - no_cache=True) - serv = nc.servers.get(instance_id) - host = serv.__getattr__('OS-EXT-SRV-ATTR:host') - - return host - def _get_provider_vlan_id(self, network): if (all(attributes.is_attr_set(network.get(attr)) for attr in (provider.NETWORK_TYPE, @@ -275,7 +260,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): pass def _invoke_nexus_for_net_create(self, context, tenant_id, net_id, - instance_id): + instance_id, host_id): if not self.config_nexus: return False @@ -287,16 +272,30 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): attachment = { const.TENANT_ID: tenant_id, const.INSTANCE_ID: instance_id, - const.HOST_NAME: self._get_instance_host(tenant_id, instance_id), + const.HOST_NAME: host_id, } self._invoke_plugin_per_device( const.NEXUS_PLUGIN, 'create_network', [network, attachment]) - @staticmethod - def _should_call_create_net(device_owner, instance_id): - return (instance_id and device_owner != 'network:dhcp') + def _check_valid_port_device_owner(self, port): + """Check the port for valid device_owner. + + Don't call the nexus plugin for router and dhcp + port owners. + """ + return port['device_owner'].startswith('compute') + + def _get_port_host_id_from_bindings(self, port): + """Get host_id from portbindings.""" + host_id = None + + if (portbindings.HOST_ID in port and + attributes.is_attr_set(port[portbindings.HOST_ID])): + host_id = port[portbindings.HOST_ID] + + return host_id def create_port(self, context, port): """Create port. @@ -309,16 +308,18 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN, self._func_name(), args) - try: - instance_id = port['port']['device_id'] - device_owner = port['port']['device_owner'] + instance_id = port['port']['device_id'] - if self._should_call_create_net(device_owner, instance_id): + # Only call nexus plugin if there's a valid instance_id, host_id + # and device_owner + try: + host_id = self._get_port_host_id_from_bindings(port['port']) + if (instance_id and host_id and + self._check_valid_port_device_owner(port['port'])): net_id = port['port']['network_id'] tenant_id = port['port']['tenant_id'] self._invoke_nexus_for_net_create( - context, tenant_id, net_id, instance_id) - + context, tenant_id, net_id, instance_id, host_id) except Exception: # Create network on the Nexus plugin has failed, so we need # to rollback the port creation on the VSwitch plugin. @@ -356,17 +357,19 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN, self._func_name(), args) - try: - net_id = old_port['network_id'] - instance_id = '' - if 'device_id' in port['port']: - instance_id = port['port']['device_id'] + net_id = old_port['network_id'] + instance_id = '' + if 'device_id' in port['port']: + instance_id = port['port']['device_id'] - # Check if there's a new device_id - if instance_id and not old_device: + # Check if there's a new device_id + try: + host_id = self._get_port_host_id_from_bindings(port['port']) + if (instance_id and not old_device and host_id and + self._check_valid_port_device_owner(port['port'])): tenant_id = old_port['tenant_id'] self._invoke_nexus_for_net_create( - context, tenant_id, net_id, instance_id) + context, tenant_id, net_id, instance_id, host_id) return ovs_output[0] except Exception: @@ -392,8 +395,11 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): """ LOG.debug(_("delete_port() called")) port = self.get_port(context, id) - exclude_list = ('', 'compute:none', 'network:dhcp') - if self.config_nexus and port['device_owner'] not in exclude_list: + + host_id = self._get_port_host_id_from_bindings(port) + + if (self.config_nexus and host_id and + self._check_valid_port_device_owner(port)): vlan_id = self._get_segmentation_id(port['network_id']) n_args = [port['device_id'], vlan_id] self._invoke_plugin_per_device(const.NEXUS_PLUGIN, @@ -411,8 +417,9 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2): tenant_id = port['tenant_id'] net_id = port['network_id'] instance_id = port['device_id'] - self._invoke_nexus_for_net_create(context, tenant_id, - net_id, instance_id) + host_id = port[portbindings.HOST_ID] + self._invoke_nexus_for_net_create(context, tenant_id, net_id, + instance_id, host_id) finally: # Raise the original exception. raise exc_info[0], exc_info[1], exc_info[2] diff --git a/neutron/tests/unit/cisco/test_network_plugin.py b/neutron/tests/unit/cisco/test_network_plugin.py index 656f00861..b336d40f6 100644 --- a/neutron/tests/unit/cisco/test_network_plugin.py +++ b/neutron/tests/unit/cisco/test_network_plugin.py @@ -26,6 +26,7 @@ from neutron.common import exceptions as q_exc from neutron import context from neutron.db import db_base_plugin_v2 as base_plugin from neutron.db import l3_db +from neutron.extensions import portbindings from neutron.extensions import providernet as provider from neutron.manager import NeutronManager from neutron.plugins.cisco.common import cisco_constants as const @@ -35,6 +36,7 @@ from neutron.plugins.cisco.db import nexus_db_v2 from neutron.plugins.cisco.models import virt_phy_sw_v2 from neutron.plugins.openvswitch.common import config as ovs_config from neutron.plugins.openvswitch import ovs_db_v2 +from neutron.tests.unit import _test_extension_portbindings as test_bindings from neutron.tests.unit import test_db_plugin LOG = logging.getLogger(__name__) @@ -83,7 +85,8 @@ class TestCiscoV2HTTPResponse(CiscoNetworkPluginV2TestCase, class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, - test_db_plugin.TestPortsV2): + test_db_plugin.TestPortsV2, + test_bindings.PortBindingsHostTestCaseMixin): def setUp(self): """Configure for end-to-end neutron testing using a mock ncclient. @@ -135,16 +138,6 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, } mock.patch.dict(cisco_config.device_dictionary, nexus_config).start() - patches = { - '_should_call_create_net': True, - '_get_instance_host': 'testhost' - } - for func in patches: - mock_sw = mock.patch.object( - virt_phy_sw_v2.VirtualPhysicalSwitchModelV2, - func).start() - mock_sw.return_value = patches[func] - super(TestCiscoPortsV2, self).setUp() @contextlib.contextmanager @@ -169,7 +162,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, @contextlib.contextmanager def _create_port_res(self, name='myname', cidr='1.0.0.0/24', - do_delete=True): + do_delete=True, host_id='testhost'): """Create a network, subnet, and port and yield the result. Create a network, subnet, and port, yield the result, @@ -181,12 +174,16 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, end of testing """ + ctx = context.get_admin_context() with self.network(name=name) as network: with self.subnet(network=network, cidr=cidr) as subnet: net_id = subnet['subnet']['network_id'] - res = self._create_port(self.fmt, net_id, - device_id='testdev', - device_owner='testowner') + args = (portbindings.HOST_ID, 'device_id', 'device_owner') + port_dict = {portbindings.HOST_ID: host_id, + 'device_id': 'testdev', + 'device_owner': 'compute:None'} + res = self._create_port(self.fmt, net_id, arg_list=args, + context=ctx, **port_dict) port = self.deserialize(self.fmt, res) try: yield res @@ -405,12 +402,9 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, a fictitious host name during port creation. """ - with mock.patch.object(virt_phy_sw_v2.VirtualPhysicalSwitchModelV2, - '_get_instance_host') as mock_get_instance: - mock_get_instance.return_value = 'fictitious_host' - with self._create_port_res(do_delete=False) as res: - self._assertExpectedHTTP(res.status_int, - c_exc.NexusComputeHostNotConfigured) + with self._create_port_res(do_delete=False, host_id='fakehost') as res: + self._assertExpectedHTTP(res.status_int, + c_exc.NexusComputeHostNotConfigured) def test_nexus_bind_fail_rollback(self): """Test for proper rollback following add Nexus DB binding failure. @@ -450,7 +444,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase, device_id = "00fff4d0-e4a8-4a3a-8906-4c4cdafb59f1" if orig_port['port']['device_id'] == device_id: device_id = "600df00d-e4a8-4a3a-8906-feed600df00d" - data = {'port': {'device_id': device_id}} + data = {'port': {'device_id': device_id, + portbindings.HOST_ID: 'testhost'}} port_id = orig_port['port']['id'] req = self.new_update_request('ports', data, port_id) res = req.get_response(self.api)