Merge "ML2 Cisco Nexus MD: VM migration support"

This commit is contained in:
Jenkins 2014-03-24 07:36:58 +00:00 committed by Gerrit Code Review
commit db0dadf533
2 changed files with 147 additions and 53 deletions

View File

@ -56,11 +56,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
cfg.CONF.ml2_cisco.managed_physical_network == cfg.CONF.ml2_cisco.managed_physical_network ==
segment[api.PHYSICAL_NETWORK]) segment[api.PHYSICAL_NETWORK])
def _get_vlanid(self, context): def _get_vlanid(self, segment):
segment = context.bound_segment
if (segment and segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN and if (segment and segment[api.NETWORK_TYPE] == p_const.TYPE_VLAN and
self._valid_network_segment(segment)): self._valid_network_segment(segment)):
return context.bound_segment.get(api.SEGMENTATION_ID) return segment.get(api.SEGMENTATION_ID)
def _is_deviceowner_compute(self, port): def _is_deviceowner_compute(self, port):
return port['device_owner'].startswith('compute') return port['device_owner'].startswith('compute')
@ -76,24 +75,22 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
else: else:
raise excep.NexusComputeHostNotConfigured(host=host_id) raise excep.NexusComputeHostNotConfigured(host=host_id)
def _configure_nxos_db(self, context, vlan_id, device_id, host_id): def _configure_nxos_db(self, vlan_id, device_id, host_id):
"""Create the nexus database entry. """Create the nexus database entry.
Called during update precommit port event. Called during update precommit port event.
""" """
port_id, switch_ip = self._get_switch_info(host_id) port_id, switch_ip = self._get_switch_info(host_id)
nxos_db.add_nexusport_binding(port_id, str(vlan_id), switch_ip, nxos_db.add_nexusport_binding(port_id, str(vlan_id), switch_ip,
device_id) device_id)
def _configure_switch_entry(self, context, vlan_id, device_id, host_id): def _configure_switch_entry(self, vlan_id, device_id, host_id):
"""Create a nexus switch entry. """Create a nexus switch entry.
if needed, create a VLAN in the appropriate switch/port and if needed, create a VLAN in the appropriate switch/port and
configure the appropriate interfaces for this VLAN. configure the appropriate interfaces for this VLAN.
Called during update postcommit port event. Called during update postcommit port event.
""" """
port_id, switch_ip = self._get_switch_info(host_id) port_id, switch_ip = self._get_switch_info(host_id)
vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id) vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
@ -109,11 +106,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name) LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id, port_id) self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id, port_id)
def _delete_nxos_db(self, context, vlan_id, device_id, host_id): def _delete_nxos_db(self, vlan_id, device_id, host_id):
"""Delete the nexus database entry. """Delete the nexus database entry.
Called during delete precommit port event. Called during delete precommit port event.
""" """
try: try:
row = nxos_db.get_nexusvm_binding(vlan_id, device_id) row = nxos_db.get_nexusvm_binding(vlan_id, device_id)
@ -122,14 +118,13 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
except excep.NexusPortBindingNotFound: except excep.NexusPortBindingNotFound:
return return
def _delete_switch_entry(self, context, vlan_id, device_id, host_id): def _delete_switch_entry(self, vlan_id, device_id, host_id):
"""Delete the nexus switch entry. """Delete the nexus switch entry.
By accessing the current db entries determine if switch By accessing the current db entries determine if switch
configuration can be removed. configuration can be removed.
Called during update postcommit port event. Called during update postcommit port event.
""" """
port_id, switch_ip = self._get_switch_info(host_id) port_id, switch_ip = self._get_switch_info(host_id)
@ -147,20 +142,25 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
except excep.NexusPortBindingNotFound: except excep.NexusPortBindingNotFound:
self.driver.delete_vlan(switch_ip, vlan_id) self.driver.delete_vlan(switch_ip, vlan_id)
def _port_action(self, context, func): def _is_vm_migration(self, context):
if not context.bound_segment and context.original_bound_segment:
return (context.current.get(portbindings.HOST_ID) !=
context.original.get(portbindings.HOST_ID))
def _port_action(self, port, segment, func):
"""Verify configuration and then process event.""" """Verify configuration and then process event."""
device_id = context.current.get('device_id') device_id = port.get('device_id')
host_id = context.current.get(portbindings.HOST_ID) host_id = port.get(portbindings.HOST_ID)
# Workaround until vlan can be retrieved during delete_port_postcommit # Workaround until vlan can be retrieved during delete_port_postcommit
# event. # event.
if func == self._delete_switch_entry: if func == self._delete_switch_entry:
vlan_id = self._delete_port_postcommit_vlan vlan_id = self._delete_port_postcommit_vlan
else: else:
vlan_id = self._get_vlanid(context) vlan_id = self._get_vlanid(segment)
if vlan_id and device_id and host_id: if vlan_id and device_id and host_id:
func(context, vlan_id, device_id, host_id) func(vlan_id, device_id, host_id)
else: else:
fields = "vlan_id " if not vlan_id else "" fields = "vlan_id " if not vlan_id else ""
fields += "device_id " if not device_id else "" fields += "device_id " if not device_id else ""
@ -176,22 +176,46 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
def update_port_precommit(self, context): def update_port_precommit(self, context):
"""Update port pre-database transaction commit event.""" """Update port pre-database transaction commit event."""
port = context.current
if self._is_deviceowner_compute(port) and self._is_status_active(port): # if VM migration is occurring then remove previous database entry
self._port_action(context, self._configure_nxos_db) # else process update event.
if self._is_vm_migration(context):
self._port_action(context.original,
context.original_bound_segment,
self._delete_nxos_db)
else:
if (self._is_deviceowner_compute(context.current) and
self._is_status_active(context.current)):
self._port_action(context.current,
context.bound_segment,
self._configure_nxos_db)
def update_port_postcommit(self, context): def update_port_postcommit(self, context):
"""Update port non-database commit event.""" """Update port non-database commit event."""
port = context.current
if self._is_deviceowner_compute(port) and self._is_status_active(port): # if VM migration is occurring then remove previous nexus switch entry
self._port_action(context, self._configure_switch_entry) # else process update event.
if self._is_vm_migration(context):
self._port_action(context.original,
context.original_bound_segment,
self._delete_switch_entry)
else:
if (self._is_deviceowner_compute(context.current) and
self._is_status_active(context.current)):
self._port_action(context.current,
context.bound_segment,
self._configure_switch_entry)
def delete_port_precommit(self, context): def delete_port_precommit(self, context):
"""Delete port pre-database commit event.""" """Delete port pre-database commit event."""
if self._is_deviceowner_compute(context.current): if self._is_deviceowner_compute(context.current):
self._port_action(context, self._delete_nxos_db) self._port_action(context.current,
context.bound_segment,
self._delete_nxos_db)
def delete_port_postcommit(self, context): def delete_port_postcommit(self, context):
"""Delete port non-database commit event.""" """Delete port non-database commit event."""
if self._is_deviceowner_compute(context.current): if self._is_deviceowner_compute(context.current):
self._port_action(context, self._delete_switch_entry) self._port_action(context.current,
context.bound_segment,
self._delete_switch_entry)

View File

@ -24,14 +24,19 @@ from neutron import context
from neutron.extensions import portbindings from neutron.extensions import portbindings
from neutron.manager import NeutronManager from neutron.manager import NeutronManager
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2 import config as ml2_config from neutron.plugins.ml2 import config as ml2_config
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import driver_context
from neutron.plugins.ml2.drivers.cisco.nexus import config as cisco_config from neutron.plugins.ml2.drivers.cisco.nexus import config as cisco_config
from neutron.plugins.ml2.drivers.cisco.nexus import exceptions as c_exc from neutron.plugins.ml2.drivers.cisco.nexus import exceptions as c_exc
from neutron.plugins.ml2.drivers.cisco.nexus import mech_cisco_nexus from neutron.plugins.ml2.drivers.cisco.nexus import mech_cisco_nexus
from neutron.plugins.ml2.drivers.cisco.nexus import nexus_db_v2
from neutron.plugins.ml2.drivers.cisco.nexus import nexus_network_driver from neutron.plugins.ml2.drivers.cisco.nexus import nexus_network_driver
from neutron.plugins.ml2.drivers import type_vlan as vlan_config from neutron.plugins.ml2.drivers import type_vlan as vlan_config
from neutron.tests.unit import test_db_plugin from neutron.tests.unit import test_db_plugin
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
ML2_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin' ML2_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin'
PHYS_NET = 'physnet1' PHYS_NET = 'physnet1'
@ -49,6 +54,12 @@ CIDR_2 = '10.0.1.0/24'
DEVICE_ID_1 = '11111111-1111-1111-1111-111111111111' DEVICE_ID_1 = '11111111-1111-1111-1111-111111111111'
DEVICE_ID_2 = '22222222-2222-2222-2222-222222222222' DEVICE_ID_2 = '22222222-2222-2222-2222-222222222222'
DEVICE_OWNER = 'compute:None' DEVICE_OWNER = 'compute:None'
BOUND_SEGMENT1 = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
api.PHYSICAL_NETWORK: PHYS_NET,
api.SEGMENTATION_ID: VLAN_START}
BOUND_SEGMENT2 = {api.NETWORK_TYPE: p_const.TYPE_VLAN,
api.PHYSICAL_NETWORK: PHYS_NET,
api.SEGMENTATION_ID: VLAN_START + 1}
class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase): class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
@ -99,24 +110,24 @@ class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
'_import_ncclient', '_import_ncclient',
return_value=self.mock_ncclient).start() return_value=self.mock_ncclient).start()
# Mock port values for 'status' and 'binding:segmentation_id' # Mock port context values for bound_segments and 'status'.
self.mock_bound_segment = mock.patch.object(
driver_context.PortContext,
'bound_segment',
new_callable=mock.PropertyMock).start()
self.mock_bound_segment.return_value = BOUND_SEGMENT1
self.mock_original_bound_segment = mock.patch.object(
driver_context.PortContext,
'original_bound_segment',
new_callable=mock.PropertyMock).start()
self.mock_original_bound_segment.return_value = None
mock_status = mock.patch.object( mock_status = mock.patch.object(
mech_cisco_nexus.CiscoNexusMechanismDriver, mech_cisco_nexus.CiscoNexusMechanismDriver,
'_is_status_active').start() '_is_status_active').start()
mock_status.return_value = n_const.PORT_STATUS_ACTIVE mock_status.return_value = n_const.PORT_STATUS_ACTIVE
def _mock_get_vlanid(context):
network = context.network.current
if network['name'] == NETWORK_NAME:
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) super(CiscoML2MechanismTestCase, self).setUp(ML2_PLUGIN)
self.port_create_status = 'DOWN' self.port_create_status = 'DOWN'
@ -210,8 +221,7 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
'admin_state_up': True}} 'admin_state_up': True}}
req = self.new_update_request('ports', data, req = self.new_update_request('ports', data,
port['port']['id']) port['port']['id'])
res = req.get_response(self.api) yield req.get_response(self.api)
yield res.status_int
def _assertExpectedHTTP(self, status, exc): def _assertExpectedHTTP(self, status, exc):
"""Confirm that an HTTP status corresponds to an expected exception. """Confirm that an HTTP status corresponds to an expected exception.
@ -310,6 +320,7 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
vlan_creation_expected=True, vlan_creation_expected=True,
add_keyword_expected=False)) add_keyword_expected=False))
self.mock_ncclient.reset_mock() self.mock_ncclient.reset_mock()
self.mock_bound_segment.return_value = BOUND_SEGMENT2
# Second vlan should be configured with 'add' keyword # Second vlan should be configured with 'add' keyword
with self._create_resources(name=NETWORK_NAME_2, with self._create_resources(name=NETWORK_NAME_2,
@ -319,6 +330,9 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
vlan_creation_expected=True, vlan_creation_expected=True,
add_keyword_expected=True)) add_keyword_expected=True))
# Return to first segment for delete port calls.
self.mock_bound_segment.return_value = BOUND_SEGMENT1
def test_nexus_connect_fail(self): def test_nexus_connect_fail(self):
"""Test failure to connect to a Nexus switch. """Test failure to connect to a Nexus switch.
@ -329,8 +343,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
""" """
with self._patch_ncclient('connect.side_effect', with self._patch_ncclient('connect.side_effect',
AttributeError): AttributeError):
with self._create_resources() as result_status: with self._create_resources() as result:
self._assertExpectedHTTP(result_status, self._assertExpectedHTTP(result.status_int,
c_exc.NexusConnectFailed) c_exc.NexusConnectFailed)
def test_nexus_vlan_config_two_hosts(self): def test_nexus_vlan_config_two_hosts(self):
@ -378,6 +392,62 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
self.assertTrue(self._is_vlan_unconfigured( self.assertTrue(self._is_vlan_unconfigured(
vlan_deletion_expected=True)) vlan_deletion_expected=True))
def test_nexus_vm_migration(self):
"""Verify VM (live) migration.
Simulate the following:
Nova informs neutron of live-migration with port-update(new host).
This should trigger two update_port_pre/postcommit() calls.
The first one should only change the current host_id and remove the
binding resulting in the mechanism drivers receiving:
PortContext.original['binding:host_id']: previous value
PortContext.original_bound_segment: previous value
PortContext.current['binding:host_id']: current (new) value
PortContext.bound_segment: None
The second one binds the new host resulting in the mechanism
drivers receiving:
PortContext.original['binding:host_id']: previous value
PortContext.original_bound_segment: None
PortContext.current['binding:host_id']: previous value
PortContext.bound_segment: new value
"""
# Create network, subnet and port.
with self._create_resources() as result:
# Verify initial database entry.
# Use port_id to verify that 1st host name was used.
binding = nexus_db_v2.get_nexusvm_binding(VLAN_START, DEVICE_ID_1)
self.assertEqual(binding.port_id, NEXUS_INTERFACE)
port = self.deserialize(self.fmt, result)
port_id = port['port']['id']
# Trigger update event to unbind segment.
# Results in port being deleted from nexus DB and switch.
data = {'port': {portbindings.HOST_ID: COMP_HOST_NAME_2}}
self.mock_bound_segment.return_value = None
self.mock_original_bound_segment.return_value = BOUND_SEGMENT1
self.new_update_request('ports', data,
port_id).get_response(self.api)
# Verify that port entry has been deleted.
self.assertRaises(c_exc.NexusPortBindingNotFound,
nexus_db_v2.get_nexusvm_binding,
VLAN_START, DEVICE_ID_1)
# Trigger update event to bind segment with new host.
self.mock_bound_segment.return_value = BOUND_SEGMENT1
self.mock_original_bound_segment.return_value = None
self.new_update_request('ports', data,
port_id).get_response(self.api)
# Verify that port entry has been added using new host name.
# Use port_id to verify that 2nd host name was used.
binding = nexus_db_v2.get_nexusvm_binding(VLAN_START, DEVICE_ID_1)
self.assertEqual(binding.port_id, NEXUS_INTERFACE_2)
def test_nexus_config_fail(self): def test_nexus_config_fail(self):
"""Test a Nexus switch configuration failure. """Test a Nexus switch configuration failure.
@ -389,8 +459,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
with self._patch_ncclient( with self._patch_ncclient(
'connect.return_value.edit_config.side_effect', 'connect.return_value.edit_config.side_effect',
AttributeError): AttributeError):
with self._create_resources() as result_status: with self._create_resources() as result:
self._assertExpectedHTTP(result_status, self._assertExpectedHTTP(result.status_int,
c_exc.NexusConfigFailed) c_exc.NexusConfigFailed)
def test_nexus_extended_vlan_range_failure(self): def test_nexus_extended_vlan_range_failure(self):
@ -409,8 +479,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
with self._patch_ncclient( with self._patch_ncclient(
'connect.return_value.edit_config.side_effect', 'connect.return_value.edit_config.side_effect',
mock_edit_config_a): mock_edit_config_a):
with self._create_resources() as result_status: with self._create_resources() as result:
self.assertEqual(result_status, wexc.HTTPOk.code) self.assertEqual(result.status_int, wexc.HTTPOk.code)
def mock_edit_config_b(target, config): def mock_edit_config_b(target, config):
if all(word in config for word in ['no', 'shutdown']): if all(word in config for word in ['no', 'shutdown']):
@ -419,8 +489,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
with self._patch_ncclient( with self._patch_ncclient(
'connect.return_value.edit_config.side_effect', 'connect.return_value.edit_config.side_effect',
mock_edit_config_b): mock_edit_config_b):
with self._create_resources() as result_status: with self._create_resources() as result:
self.assertEqual(result_status, wexc.HTTPOk.code) self.assertEqual(result.status_int, wexc.HTTPOk.code)
def test_nexus_vlan_config_rollback(self): def test_nexus_vlan_config_rollback(self):
"""Test rollback following Nexus VLAN state config failure. """Test rollback following Nexus VLAN state config failure.
@ -437,11 +507,11 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
with self._patch_ncclient( with self._patch_ncclient(
'connect.return_value.edit_config.side_effect', 'connect.return_value.edit_config.side_effect',
mock_edit_config): mock_edit_config):
with self._create_resources() as result_status: with self._create_resources() as result:
# Confirm that the last configuration sent to the Nexus # Confirm that the last configuration sent to the Nexus
# switch was deletion of the VLAN. # switch was deletion of the VLAN.
self.assertTrue(self._is_in_last_nexus_cfg(['<no>', '<vlan>'])) self.assertTrue(self._is_in_last_nexus_cfg(['<no>', '<vlan>']))
self._assertExpectedHTTP(result_status, self._assertExpectedHTTP(result.status_int,
c_exc.NexusConfigFailed) c_exc.NexusConfigFailed)
def test_nexus_host_not_configured(self): def test_nexus_host_not_configured(self):
@ -451,8 +521,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
a fictitious host name during port creation. a fictitious host name during port creation.
""" """
with self._create_resources(host_id='fake_host') as result_status: with self._create_resources(host_id='fake_host') as result:
self._assertExpectedHTTP(result_status, self._assertExpectedHTTP(result.status_int,
c_exc.NexusComputeHostNotConfigured) c_exc.NexusComputeHostNotConfigured)
def test_nexus_missing_fields(self): def test_nexus_missing_fields(self):
@ -462,8 +532,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
empty host_id and device_id values during port creation. empty host_id and device_id values during port creation.
""" """
with self._create_resources(device_id='', host_id='') as result_status: with self._create_resources(device_id='', host_id='') as result:
self._assertExpectedHTTP(result_status, self._assertExpectedHTTP(result.status_int,
c_exc.NexusMissingRequiredFields) c_exc.NexusMissingRequiredFields)