ML2 Mechanism Driver for Cisco Nexus platforms.
"""
-from novaclient.v1_1 import client as nova_client
from oslo.config import cfg
+from neutron.common import constants as n_const
+from neutron.extensions import portbindings
from neutron.openstack.common import excutils
from neutron.openstack.common import log as logging
from neutron.plugins.ml2 import driver_api as api
# Initialize credential store after database initialization
cred.Store.initialize()
- def _get_vlanid(self, port_context):
- """Return the VLAN ID (segmentation ID) for this network."""
- # NB: Currently only a single physical network is supported.
- network_context = port_context.network
- network_segments = network_context.network_segments
- return network_segments[0]['segmentation_id']
+ def _valid_network_segment(self, segment):
+ return (cfg.CONF.ml2_cisco.managed_physical_network is None or
+ cfg.CONF.ml2_cisco.managed_physical_network ==
+ segment[api.PHYSICAL_NETWORK])
+
+ def _get_vlanid(self, context):
+ segment = context.bound_segment
+ if (segment and segment[api.NETWORK_TYPE] == 'vlan' and
+ self._valid_network_segment(segment)):
+ return context.bound_segment.get(api.SEGMENTATION_ID)
+
+ def _is_deviceowner_compute(self, port):
+ return port['device_owner'].startswith('compute')
+
+ def _is_status_active(self, port):
+ return port['status'] == n_const.PORT_STATUS_ACTIVE
def _get_credential(self, nexus_ip):
"""Return credential information for a given Nexus IP address.
self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id,
port_id)
- # TODO(rcurran) Temporary access to host_id. When available use
- # port-binding to access host name.
- def _get_instance_host(self, 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 _invoke_nexus_on_port_event(self, context, instance_id):
- """Prepare variables for call to nexus switch."""
+ def _invoke_nexus_on_port_event(self, context):
vlan_id = self._get_vlanid(context)
- host = self._get_instance_host(instance_id)
-
- # Trunk segmentation id for only this host
- vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
- self._manage_port(vlan_name, vlan_id, host, instance_id)
-
- def create_port_postcommit(self, context):
- """Create port post-database commit event."""
- port = context.current
- instance_id = port['device_id']
- device_owner = port['device_owner']
+ host_id = context.current.get(portbindings.HOST_ID)
- if instance_id and device_owner != 'network:dhcp':
- self._invoke_nexus_on_port_event(context, instance_id)
+ if vlan_id and host_id:
+ vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
+ instance_id = context.current.get('device_id')
+ self._manage_port(vlan_name, vlan_id, host_id, instance_id)
+ else:
+ LOG.debug(_("Vlan ID %(vlan_id)s or Host ID %(host_id)s missing."),
+ {'vlan_id': vlan_id, 'host_id': host_id})
def update_port_postcommit(self, context):
"""Update port post-database commit event."""
port = context.current
- old_port = context.original
- old_device = old_port['device_id']
- instance_id = port['device_id'] if 'device_id' in port else ""
- # Check if there's a new device_id
- if instance_id and not old_device:
- self._invoke_nexus_on_port_event(context, instance_id)
+ if self._is_deviceowner_compute(port) and self._is_status_active(port):
+ self._invoke_nexus_on_port_event(context)
def delete_port_precommit(self, context):
"""Delete port pre-database commit event.
Delete port bindings from the database and scan whether the network
is still required on the interfaces trunked.
"""
+
+ if not self._is_deviceowner_compute(context.current):
+ return
+
port = context.current
device_id = port['device_id']
vlan_id = self._get_vlanid(context)
+ if not vlan_id or not device_id:
+ return
+
# Delete DB row for this port
try:
row = nxos_db.get_nexusvm_binding(vlan_id, device_id)
import webob.exc as wexc
from neutron.api.v2 import base
+from neutron.common import constants as n_const
from neutron import context
+from neutron.extensions import portbindings
from neutron.manager import NeutronManager
from neutron.openstack.common import log as logging
from neutron.plugins.ml2 import config as ml2_config
# Configure the ML2 mechanism drivers and network types
ml2_opts = {
- 'mechanism_drivers': ['cisco_nexus', 'logger', 'test'],
+ 'mechanism_drivers': ['cisco_nexus'],
'tenant_network_types': ['vlan'],
}
for opt, val in ml2_opts.items():
'_import_ncclient',
return_value=self.mock_ncclient).start()
- # Use COMP_HOST_NAME as the compute node host name.
- mock_host = mock.patch.object(
+ # Mock port values for 'status' and 'binding:segmenation_id'
+ mock_status = mock.patch.object(
mech_cisco_nexus.CiscoNexusMechanismDriver,
- '_get_instance_host').start()
- mock_host.return_value = COMP_HOST_NAME
+ '_is_status_active').start()
+ mock_status.return_value = n_const.PORT_STATUS_ACTIVE
+
+ def _mock_get_vlanid(context):
+ port = context.current
+ if port['device_id'] == DEVICE_ID_1:
+ return VLAN_START
+ else:
+ return VLAN_START + 1
+
+ mock_vlanid = mock.patch.object(
+ mech_cisco_nexus.CiscoNexusMechanismDriver,
+ '_get_vlanid').start()
+ mock_vlanid.side_effect = _mock_get_vlanid
super(CiscoML2MechanismTestCase, self).setUp(ML2_PLUGIN)
test_db_plugin.TestPortsV2):
@contextlib.contextmanager
- def _create_port_res(self, name='myname', cidr=CIDR_1,
- device_id=DEVICE_ID_1, do_delete=True):
+ def _create_resources(self, name='myname', cidr=CIDR_1,
+ device_id=DEVICE_ID_1,
+ host_id=COMP_HOST_NAME,
+ expected_exception=None):
"""Create network, subnet, and port resources for test cases.
- Create a network, subnet, and port, yield the result,
+ Create a network, subnet, port and then update port, yield the result,
then delete the port, subnet, and network.
- :param name: Name of network to be created
- :param cidr: cidr address of subnetwork to be created
- :param device_id: Device ID to use for port to be created
- :param do_delete: If set to True, delete the port at the
- end of testing
+ :param name: Name of network to be created.
+ :param cidr: cidr address of subnetwork to be created.
+ :param device_id: Device ID to use for port to be created/updated.
+ :param host_id: Host ID to use for port create/update.
+ :param expected_exception: Expected HTTP code.
"""
+ 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=device_id)
+ args = (portbindings.HOST_ID, 'device_id', 'device_owner',
+ 'admin_state_up')
+ port_dict = {portbindings.HOST_ID: host_id,
+ 'device_id': device_id,
+ 'device_owner': 'compute:none',
+ 'admin_state_up': True}
+
+ res = self._create_port(self.fmt, net_id, arg_list=args,
+ context=ctx, **port_dict)
port = self.deserialize(self.fmt, res)
+
+ expected_exception = self._expectedHTTP(expected_exception)
+ data = {'port': port_dict}
+ self._update('ports', port['port']['id'], data,
+ expected_code=expected_exception,
+ neutron_context=ctx)
+
try:
- yield res
+ yield port
finally:
- if do_delete:
- self._delete('ports', port['port']['id'])
+ self._delete('ports', port['port']['id'])
- def _assertExpectedHTTP(self, status, exc):
- """Confirm that an HTTP status corresponds to an expected exception.
+ def _expectedHTTP(self, exc):
+ """Map a Cisco exception to the HTTP status equivalent.
- Confirm that an HTTP status which has been returned for an
- neutron API request matches the HTTP status corresponding
- to an expected exception.
-
- :param status: HTTP status
- :param exc: Expected exception
+ :param exc: Expected Cisco exception
"""
- if exc in base.FAULT_MAP:
+ if exc == None:
+ expected_http = wexc.HTTPOk.code
+ elif exc in base.FAULT_MAP:
expected_http = base.FAULT_MAP[exc].code
else:
expected_http = wexc.HTTPInternalServerError.code
- self.assertEqual(status, expected_http)
+
+ return expected_http
def test_create_ports_bulk_emulated_plugin_failure(self):
real_has_attr = hasattr
the command staring sent to the switch contains the keyword 'add'.
"""
- with self._create_port_res(name='net1', cidr=CIDR_1):
+ with self._create_resources(name='net1', cidr=CIDR_1):
self.assertTrue(self._is_in_last_nexus_cfg(['allowed', 'vlan']))
self.assertFalse(self._is_in_last_nexus_cfg(['add']))
- with self._create_port_res(name='net2', cidr=CIDR_2):
+ with self._create_resources(name='net2', device_id=DEVICE_ID_2,
+ cidr=CIDR_2):
self.assertTrue(
self._is_in_last_nexus_cfg(['allowed', 'vlan', 'add']))
"""
with self._patch_ncclient('connect.side_effect',
AttributeError):
- with self._create_port_res(do_delete=False) as res:
- self._assertExpectedHTTP(res.status_int,
- c_exc.NexusConnectFailed)
+ self._create_resources(expected_exception=c_exc.NexusConnectFailed)
def test_nexus_config_fail(self):
"""Test a Nexus switch configuration failure.
with self._patch_ncclient(
'connect.return_value.edit_config.side_effect',
AttributeError):
- with self._create_port_res(do_delete=False) as res:
- self._assertExpectedHTTP(res.status_int,
- c_exc.NexusConfigFailed)
+ self._create_resources(expected_exception=c_exc.NexusConfigFailed)
def test_nexus_extended_vlan_range_failure(self):
"""Test that extended VLAN range config errors are ignored.
with self._patch_ncclient(
'connect.return_value.edit_config.side_effect',
mock_edit_config_a):
- with self._create_port_res(name='myname') as res:
- self.assertEqual(res.status_int, wexc.HTTPCreated.code)
+ self._create_resources(name='myname')
def mock_edit_config_b(target, config):
if all(word in config for word in ['no', 'shutdown']):
with self._patch_ncclient(
'connect.return_value.edit_config.side_effect',
mock_edit_config_b):
- with self._create_port_res(name='myname') as res:
- self.assertEqual(res.status_int, wexc.HTTPCreated.code)
+ self._create_resources(name='myname')
def test_nexus_vlan_config_rollback(self):
"""Test rollback following Nexus VLAN state config failure.
with self._patch_ncclient(
'connect.return_value.edit_config.side_effect',
mock_edit_config):
- with self._create_port_res(name='myname', do_delete=False) as res:
+ with self._create_resources(
+ name='myname',
+ expected_exception=c_exc.NexusConfigFailed):
# Confirm that the last configuration sent to the Nexus
# switch was deletion of the VLAN.
- self.assertTrue(
- self._is_in_last_nexus_cfg(['<no>', '<vlan>'])
- )
- self._assertExpectedHTTP(res.status_int,
- c_exc.NexusConfigFailed)
+ self.assertTrue(self._is_in_last_nexus_cfg(['<no>', '<vlan>']))
def test_nexus_host_not_configured(self):
"""Test handling of a NexusComputeHostNotConfigured exception.
a fictitious host name during port creation.
"""
- with mock.patch.object(mech_cisco_nexus.CiscoNexusMechanismDriver,
- '_get_instance_host') as mock_get_host:
- mock_get_host.return_value = 'fictitious_host'
- with self._create_port_res(do_delete=False) as res:
- self._assertExpectedHTTP(res.status_int,
- c_exc.NexusComputeHostNotConfigured)
+ self._create_resources(
+ host_id='fake_host',
+ expected_exception=c_exc.NexusComputeHostNotConfigured)
def test_nexus_bind_fail_rollback(self):
"""Test for proper rollback following add Nexus DB binding failure.
with mock.patch.object(nexus_db_v2,
'add_nexusport_binding',
side_effect=KeyError):
- with self._create_port_res(do_delete=False) as res:
+ with self._create_resources(expected_exception=KeyError):
# Confirm that the last configuration sent to the Nexus
# switch was a removal of vlan from the test interface.
self.assertTrue(
self._is_in_last_nexus_cfg(['<vlan>', '<remove>'])
)
- self._assertExpectedHTTP(res.status_int, KeyError)
def test_nexus_delete_port_rollback(self):
"""Test for proper rollback for nexus plugin delete port failure.
nexus switch during a delete_port operation.
"""
- with self._create_port_res() as res:
-
- port = self.deserialize(self.fmt, res)
-
+ with self._create_resources() as port:
# Check that there is only one binding in the nexus database
# for this VLAN/nexus switch.
start_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
import mock
import testtools
+from neutron.common import constants as n_const
from neutron.db import api as db
+from neutron.extensions import portbindings
from neutron.openstack.common import importutils
+from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers.cisco import constants
from neutron.plugins.ml2.drivers.cisco import exceptions
from neutron.plugins.ml2.drivers.cisco import mech_cisco_nexus
VLAN_ID_PC = 268
DEVICE_OWNER = 'compute:test'
NEXUS_SSH_PORT = '22'
+PORT_STATE = n_const.PORT_STATUS_ACTIVE
+NETWORK_TYPE = 'vlan'
NEXUS_DRIVER = ('neutron.plugins.ml2.drivers.cisco.'
'nexus_network_driver.CiscoNexusDriver')
"""Network context for testing purposes only."""
def __init__(self, segment_id):
- self._network_segments = [{'segmentation_id': segment_id}]
+ self._network_segments = {api.SEGMENTATION_ID: segment_id,
+ api.NETWORK_TYPE: NETWORK_TYPE}
@property
def network_segments(self):
"""Port context for testing purposes only."""
- def __init__(self, device_id, network_context):
+ def __init__(self, device_id, host_name, network_context):
self._port = {
+ 'status': PORT_STATE,
'device_id': device_id,
- 'device_owner': DEVICE_OWNER
+ 'device_owner': DEVICE_OWNER,
+ portbindings.HOST_ID: host_name
}
self._network = network_context
+ self._segment = network_context.network_segments
@property
def current(self):
def network(self):
return self._network
+ @property
+ def bound_segment(self):
+ return self._segment
+
class TestCiscoNexusDevice(base.BaseTestCase):
vlan_id = port_config.vlan_id
network_context = FakeNetworkContext(vlan_id)
- port_context = FakePortContext(instance_id, network_context)
-
- with mock.patch.object(mech_cisco_nexus.CiscoNexusMechanismDriver,
- '_get_instance_host') as mock_host:
- mock_host.return_value = host_name
-
- self._cisco_mech_driver.create_port_postcommit(port_context)
- bindings = nexus_db_v2.get_nexusport_binding(nexus_port,
- vlan_id,
- nexus_ip_addr,
- instance_id)
- self.assertEqual(len(bindings), 1)
-
- self._cisco_mech_driver.delete_port_precommit(port_context)
- with testtools.ExpectedException(
- exceptions.NexusPortBindingNotFound):
- nexus_db_v2.get_nexusport_binding(nexus_port,
- vlan_id,
- nexus_ip_addr,
- instance_id)
+ port_context = FakePortContext(instance_id, host_name,
+ network_context)
+
+ self._cisco_mech_driver.update_port_postcommit(port_context)
+ bindings = nexus_db_v2.get_nexusport_binding(nexus_port,
+ vlan_id,
+ nexus_ip_addr,
+ instance_id)
+ self.assertEqual(len(bindings), 1)
+
+ self._cisco_mech_driver.delete_port_precommit(port_context)
+ with testtools.ExpectedException(exceptions.NexusPortBindingNotFound):
+ nexus_db_v2.get_nexusport_binding(nexus_port,
+ vlan_id,
+ nexus_ip_addr,
+ instance_id)
def test_create_delete_ports(self):
"""Tests creation and deletion of two new virtual Ports."""