Cisco plugin portbinding extension support

This commit adds portbinding extension support to the Cisco plugin.

Change-Id: I87554607860b040b693edeecc2706ca8edbe49b6
Fixes: Bug #1218033
This commit is contained in:
Arvind Somya 2013-08-29 13:23:52 -04:00
parent 984d6f2b50
commit 3cc8e78956
2 changed files with 64 additions and 62 deletions

View File

@ -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]

View File

@ -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)