Pre-allocate trunk adapter on target host

This change will allocate the trunk adapter on the target server as part
of its live migration command when paired with OVS.

This reduces the amount of down time when running a live migration with
Open vSwitch as the VIF type.

Closes-Bug: #1632366

Change-Id: I43f06ecd827bb1d08636c6e53efcad292d827b20
This commit is contained in:
Drew Thorstensen 2016-08-02 14:57:02 -04:00
parent 6c6c355705
commit 5499e1b13a
7 changed files with 382 additions and 166 deletions

View File

@ -23,7 +23,8 @@ from nova.objects import migrate_data
@obj_base.NovaObjectRegistry.register
class PowerVMLiveMigrateData(migrate_data.LiveMigrateData):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added the Virtual Ethernet Adapter VLAN mappings.
VERSION = '1.1'
fields = {
'host_mig_data': fields.DictOfNullableStringsField(),
@ -33,4 +34,5 @@ class PowerVMLiveMigrateData(migrate_data.LiveMigrateData):
'public_key': fields.StringField(),
'dest_proc_compat': fields.StringField(),
'vol_data': fields.DictOfNullableStringsField(),
'vea_vlan_mappings': fields.DictOfNullableStringsField(),
}

View File

@ -1936,6 +1936,10 @@ class TestPowerVMDriver(test.TestCase):
def test_rollbk_lpm_dest(self):
self.drv.rollback_live_migration_at_destination(
'context', self.lpm_inst, 'network_info', 'block_device_info')
mock_rollback = self.lpm.rollback_live_migration_at_destination
mock_rollback.assert_called_once_with(
'context', self.lpm_inst, 'network_info', 'block_device_info',
destroy_disks=True, migrate_data=None)
self.assertRaises(
KeyError, lambda: self.drv.live_migrations[self.lpm_inst.uuid])

View File

@ -23,6 +23,7 @@ import mock
from nova import exception
from nova import objects
from nova import test
from nova.tests.unit import fake_network
from nova_powervm.objects import migrate_data as mig_obj
from nova_powervm.tests.virt import powervm
@ -41,6 +42,10 @@ class TestLPM(test.TestCase):
self.inst = objects.Instance(**powervm.TEST_INSTANCE)
self.network_infos = fake_network.fake_get_instance_nw_info(self, 1)
self.inst.info_cache = objects.InstanceInfoCache(
network_info=self.network_infos)
self.mig_data = mig_obj.PowerVMLiveMigrateData()
self.mig_data.host_mig_data = {}
self.mig_data.dest_ip = '1'
@ -49,6 +54,7 @@ class TestLPM(test.TestCase):
self.mig_data.public_key = 'PublicKey'
self.mig_data.dest_proc_compat = 'a,b,c'
self.mig_data.vol_data = {}
self.mig_data.vea_vlan_mappings = {}
self.lpmsrc = lpm.LiveMigrationSrc(self.drv, self.inst, self.mig_data)
self.lpmdst = lpm.LiveMigrationDest(self.drv, self.inst)
@ -151,12 +157,30 @@ class TestLPM(test.TestCase):
src_compute_info, dst_compute_info)
@mock.patch('pypowervm.tasks.storage.ComprehensiveScrub')
def test_pre_live_mig(self, mock_scrub):
@mock.patch('nova_powervm.virt.powervm.vif.'
'pre_live_migrate_at_destination')
def test_pre_live_mig(self, mock_vif_pre, mock_scrub):
vol_drv = mock.MagicMock()
network_infos = [{'type': 'pvm_sea'}]
def update_vea_mapping(adapter, host_uuid, instance, network_info,
vea_vlan_mappings):
# Make sure what comes in is None, but that we change it.
self.assertEqual(vea_vlan_mappings, {})
vea_vlan_mappings['test'] = 'resp'
mock_vif_pre.side_effect = update_vea_mapping
resp = self.lpmdst.pre_live_migration(
'context', 'block_device_info', 'network_info', 'disk_info',
'context', 'block_device_info', network_infos, 'disk_info',
self.mig_data, [vol_drv])
# Make sure the pre_live_migrate_at_destination was invoked for the vif
mock_vif_pre.assert_called_once_with(
self.drv.adapter, self.drv.host_uuid, self.inst, network_infos[0],
mock.ANY)
self.assertEqual({'test': 'resp'}, self.mig_data.vea_vlan_mappings)
# Make sure we get something back, and that the volume driver was
# invoked.
self.assertIsNotNone(resp)
@ -171,7 +195,7 @@ class TestLPM(test.TestCase):
Exception('foo'))
self.assertRaises(
exception.MigrationPreCheckError, self.lpmdst.pre_live_migration,
'context', 'block_device_info', 'network_info', 'disk_info',
'context', 'block_device_info', network_infos, 'disk_info',
self.mig_data, [vol_drv, raising_vol_drv])
vol_drv.pre_live_migration_on_destination.assert_called_once_with({})
(raising_vol_drv.pre_live_migration_on_destination.
@ -185,7 +209,14 @@ class TestLPM(test.TestCase):
vol_drv.cleanup_volume_at_destination.assert_called_once_with({})
@mock.patch('pypowervm.tasks.migration.migrate_lpar')
def test_live_migration(self, mock_migr):
@mock.patch('nova_powervm.virt.powervm.live_migration.LiveMigrationSrc.'
'_convert_nl_io_mappings')
@mock.patch('nova_powervm.virt.powervm.vif.pre_live_migrate_at_source')
def test_live_migration(self, mock_vif_pre_lpm, mock_convert_mappings,
mock_migr):
mock_trunk = mock.MagicMock()
mock_vif_pre_lpm.return_value = [mock_trunk]
mock_convert_mappings.return_value = ['AABBCCDDEEFF/5']
self.lpmsrc.lpar_w = mock.Mock()
self.lpmsrc.live_migration('context', self.mig_data)
@ -193,7 +224,12 @@ class TestLPM(test.TestCase):
self.lpmsrc.lpar_w, 'a', sdn_override=True, tgt_mgmt_svr='1',
tgt_mgmt_usr='neo', validate_only=False,
virtual_fc_mappings=None, virtual_scsi_mappings=None,
vlan_check_override=True)
vlan_check_override=True, vlan_mappings=['AABBCCDDEEFF/5'])
# Network assertions
mock_vif_pre_lpm.assert_called_once_with(
self.drv.adapter, self.drv.host_uuid, self.inst, mock.ANY)
mock_trunk.delete.assert_called_once()
# Test that we raise errors received during migration
mock_migr.side_effect = ValueError()
@ -203,20 +239,18 @@ class TestLPM(test.TestCase):
self.lpmsrc.lpar_w, 'a', sdn_override=True, tgt_mgmt_svr='1',
tgt_mgmt_usr='neo', validate_only=False,
virtual_fc_mappings=None, virtual_scsi_mappings=None,
vlan_check_override=True)
vlan_mappings=['AABBCCDDEEFF/5'], vlan_check_override=True)
@mock.patch('nova_powervm.virt.powervm.vif.post_live_migrate_at_source')
def test_post_live_mig_src(self, mock_post_migrate_vif):
network_infos = [mock.Mock(), mock.Mock()]
self.lpmsrc.post_live_migration_at_source(network_infos)
self.assertEqual(2, mock_post_migrate_vif.call_count)
def test_convert_nl_io_mappings(self):
# Test simple None case
self.assertIsNone(self.lpmsrc._convert_nl_io_mappings(None))
@mock.patch('nova_powervm.virt.powervm.vif.'
'post_live_migrate_at_destination')
def test_post_live_mig_dest(self, mock_post_migrate_vif):
network_infos = [mock.Mock(), mock.Mock()]
self.lpmdst.post_live_migration_at_destination(network_infos, [])
self.assertEqual(2, mock_post_migrate_vif.call_count)
# Do some mappings
test_mappings = {'aa:bb:cc:dd:ee:ff': 5, 'aa:bb:cc:dd:ee:ee': 126}
expected = ['AABBCCDDEEFF/5', 'AABBCCDDEEEE/126']
self.assertEqual(
set(expected),
set(self.lpmsrc._convert_nl_io_mappings(test_mappings)))
@mock.patch('pypowervm.tasks.migration.migrate_recover')
def test_rollback(self, mock_migr):

View File

@ -18,15 +18,17 @@ import mock
from nova import exception
from nova import test
from oslo_config import cfg
from pypowervm import exceptions as pvm_ex
from pypowervm.tests import test_fixtures as pvm_fx
from pypowervm import util as pvm_util
from pypowervm.wrappers import logical_partition as pvm_lpar
from pypowervm.wrappers import managed_system as pvm_ms
from pypowervm.wrappers import network as pvm_net
from nova_powervm.virt.powervm import vif
CONF = cfg.CONF
def cna(mac):
"""Builds a mock Client Network Adapter for unit tests."""
@ -249,6 +251,29 @@ class TestVifFunctions(test.TestCase):
vif._build_vif_driver, self.adpt, 'host_uuid',
mock_inst, {'type': 'bad'})
@mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_pre_live_migrate_at_source(self, mock_build_vif_drv):
mock_drv = mock.MagicMock()
mock_build_vif_drv.return_value = mock_drv
mock_vif = mock.MagicMock()
vif.pre_live_migrate_at_source(self.adpt, 'host_uuid', mock.Mock(),
mock_vif)
mock_drv.pre_live_migrate_at_source.assert_called_once_with(mock_vif)
@mock.patch('nova_powervm.virt.powervm.vif._build_vif_driver')
def test_pre_live_migrate_at_destination(self, mock_build_vif_drv):
mock_drv = mock.MagicMock()
mock_build_vif_drv.return_value = mock_drv
mock_vif = mock.MagicMock()
vif.pre_live_migrate_at_destination(self.adpt, 'host_uuid',
mock.Mock(), mock_vif, {})
mock_drv.pre_live_migrate_at_destination.assert_called_once_with(
mock_vif, {})
class TestVifSriovDriver(test.TestCase):
@ -464,14 +489,6 @@ class TestVifSeaDriver(test.TestCase):
self.assertEqual(0, cnas[1].delete.call_count)
self.assertEqual(1, cnas[2].delete.call_count)
def test_post_live_migrate_at_destination(self):
# Make sure the no-op works properly
self.drv.post_live_migrate_at_destination(mock.Mock())
def test_post_live_migrate_at_source(self):
# Make sure the no-op works properly
self.drv.post_live_migrate_at_source(mock.Mock())
class TestVifLBDriver(test.TestCase):
@ -691,72 +708,86 @@ class TestVifOvsDriver(test.TestCase):
# Validate the OVS port delete call was made
mock_del_ovs_port.assert_called_with('br-int', 'fake_dev')
@mock.patch('pypowervm.tasks.cna.find_trunks')
@mock.patch('pypowervm.wrappers.network.CNA.search')
@mock.patch('pypowervm.util.sanitize_mac_for_api')
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
def test_pre_live_migrate_at_source(self, mock_pvm_uuid, mock_sanitize,
mock_cna_search, mock_trunk_find):
# Set up the mocks
vif = {'address': 'aa:bb:cc:dd:ee:ff'}
mock_sanitize.return_value = 'AABBCCDDEEFF'
mock_trunk_find.return_value = 'trunk'
mock_pvm_uuid.return_value = 'pvm_uuid'
resp = self.drv.pre_live_migrate_at_source(vif)
self.assertEqual(resp, 'trunk')
# Make sure the APIs were called correctly
mock_sanitize.assert_called_once_with(vif['address'])
mock_cna_search.assert_called_once_with(
self.adpt, parent_type=pvm_lpar.LPAR.schema_type,
parent_uuid='pvm_uuid', one_result=True, mac='AABBCCDDEEFF')
mock_trunk_find.assert_called_once_with(self.adpt, mock.ANY)
@mock.patch('nova.network.linux_net.create_ovs_vif_port')
@mock.patch('nova.utils.execute')
@mock.patch('nova_powervm.virt.powervm.vif.PvmOvsVifDriver.'
'get_trunk_dev_name')
@mock.patch('nova_powervm.virt.powervm.vm.get_pvm_uuid')
@mock.patch('pypowervm.tasks.cna.crt_trunk_with_free_vlan')
@mock.patch('pypowervm.tasks.partition.get_this_partition')
@mock.patch('pypowervm.wrappers.network.CNA.bld')
def test_pre_live_migrate_at_destination(
self, mock_part_get, mock_trunk_crt, mock_execute,
mock_crt_ovs_port):
# Mock the vif
vif = {'devname': 'tap-dev', 'address': 'aa:bb:cc:dd:ee:ff',
'network': {'bridge': 'br-int'}, 'id': 'vif_id'}
# Mock out the management partition
mock_mgmt_wrap = mock.MagicMock()
mock_mgmt_wrap.uuid = 'mgmt_uuid'
mock_part_get.return_value = mock_mgmt_wrap
mock_trunk_crt.return_value = [mock.Mock(pvid=2)]
# Invoke and test the basic response
vea_vlan_mappings = {}
self.drv.pre_live_migrate_at_destination(vif, vea_vlan_mappings)
self.assertEqual(vea_vlan_mappings['aa:bb:cc:dd:ee:ff'], 2)
# Now validate it called the things it needed to
mock_execute.assert_called_once_with('ip', 'link', 'set', 'tap-dev',
'up', run_as_root=True)
mock_trunk_crt.assert_called_once_with(
self.adpt, 'host_uuid', ['mgmt_uuid'],
CONF.powervm.pvm_vswitch_for_novalink_io, dev_name='tap-dev')
mock_crt_ovs_port.assert_called_once_with(
'br-int', 'tap-dev', 'vif_id', 'aa:bb:cc:dd:ee:ff', self.inst.uuid)
@mock.patch('pypowervm.wrappers.network.CNA.search')
@mock.patch('pypowervm.tasks.cna.assign_free_vlan')
@mock.patch('pypowervm.tasks.partition.get_this_partition')
@mock.patch('pypowervm.wrappers.network.VSwitch.search')
def test_post_live_migrate_at_destination(
self, mock_vswitch_search, mock_assign_vlan, mock_cna_search,
mock_trunk_bld, mock_mgmt_lpar, mock_uuid, mock_dev_name, mock_exec,
mock_crt_ovs_vif_port):
# Mock the vif argument
def getitem(name):
return fake_vif[name]
fake_vif = {'address': 'aa:bb:cc:dd:ee:ff',
'network': {'bridge': 'br-int'}}
mock_vif = mock.MagicMock()
mock_vif.__getitem__.side_effect = getitem
# Mock other values
mock_assign_vlan.return_value = mock.MagicMock(pvid=70, enabled=True)
mock_cna = mock.MagicMock(pvid=35, enabled=False)
mock_cna_search.return_value = mock_cna
def test_rollback_live_migrate_at_destination(
self, mock_vs_search, mock_get_part, mock_cna_search):
# All the fun mocking
mock_vs_search.return_value = mock.MagicMock(switch_id=5)
vea_vlan_mappings = {'aa:bb:cc:dd:ee:ff': 3, 'aa:bb:cc:dd:ee:ee': 4}
vif = {'address': 'aa:bb:cc:dd:ee:ee'}
mock_get_part.return_value = mock.MagicMock(schema_type='VIO',
uuid='uuid')
mock_trunk = mock.MagicMock()
mock_trunk_bld.return_value = mock_trunk
mock_mgmt_lpar.return_value = mock.Mock(uuid='mgmt_uuid')
mock_vswitch = mock.MagicMock()
mock_vswitch_search.return_value = mock_vswitch
mock_uuid.return_value = 'lpar_uuid'
mock_dev_name.return_value = 'dev_name'
mac = pvm_util.sanitize_mac_for_api(fake_vif['address'])
self.drv.adapter = self.adpt
mock_cna_search.return_value = mock_trunk
# Execute and verify the results
self.drv.post_live_migrate_at_destination(mock_vif)
mock_assign_vlan.assert_called_once_with(
self.drv.adapter, self.drv.host_uuid, mock_vswitch, mock_cna)
mock_trunk_bld(
self.drv.adapter, pvid=70, vlan_ids=[], vswitch=mock_vswitch)
mock_trunk.create.assert_called_once_with(
parent=mock_mgmt_lpar.return_value)
# Invoke
self.drv.rollback_live_migrate_at_destination(vif, vea_vlan_mappings)
# Make sure the trunk was deleted
mock_trunk.delete.assert_called_once()
# Now make sure the calls were done correctly to actually produce a
# trunk adapter
mock_vs_search.assert_called_once_with(
self.drv.adapter, parent_type=pvm_ms.System, one_result=True,
name=CONF.powervm.pvm_vswitch_for_novalink_io)
mock_get_part.assert_called_once_with(self.drv.adapter)
mock_cna_search.assert_called_once_with(
self.adpt, mac=mac, one_result=True,
parent_type=pvm_lpar.LPAR, parent_uuid='lpar_uuid')
@mock.patch('pypowervm.tasks.cna.find_orphaned_trunks')
@mock.patch('nova.network.linux_net.delete_ovs_vif_port')
@mock.patch('nova_powervm.virt.powervm.vif.PvmOvsVifDriver.'
'get_trunk_dev_name')
def test_post_live_migrate_at_source(self, mock_trunk_dev_name,
mock_del_ovs_port,
mock_find_orphan):
t1, t2 = mock.MagicMock(), mock.MagicMock()
mock_find_orphan.return_value = [t1, t2]
mock_trunk_dev_name.return_value = 'fake_dev1'
mock_vif = {'network': {'bridge': 'br-int'}}
self.drv.post_live_migrate_at_source(mock_vif)
# The orphans should have been deleted
self.assertTrue(t1.delete.called)
self.assertTrue(t2.delete.called)
# Validate the OVS port delete call was made twice
mock_del_ovs_port.assert_called_once_with('br-int', 'fake_dev1')
self.drv.adapter, parent_type='VIO', parent_uuid='uuid',
vswitch_id=5, pvid=4, one_result=True)

View File

@ -1612,6 +1612,13 @@ class PowerVMDriver(driver.ComputeDriver):
:param migrate_data: a LiveMigrateData object
"""
# Run the rollback
mig = self.live_migrations[instance.uuid]
mig.rollback_live_migration_at_destination(
context, instance, network_info, block_device_info,
destroy_disks=destroy_disks, migrate_data=migrate_data)
# Remove the active migration
del self.live_migrations[instance.uuid]
def check_instance_shared_storage_local(self, context, instance):

View File

@ -23,6 +23,7 @@ from pypowervm.tasks import management_console as mgmt_task
from pypowervm.tasks import migration as mig
from pypowervm.tasks import storage as stor_task
from pypowervm.tasks import vterm
from pypowervm import util
import six
from nova_powervm import conf as cfg
@ -129,14 +130,14 @@ class LiveMigrationDest(LiveMigration):
return self.mig_data
def pre_live_migration(self, context, block_device_info, network_info,
def pre_live_migration(self, context, block_device_info, network_infos,
disk_info, migrate_data, vol_drvs):
"""Prepare an instance for live migration
:param context: security context
:param block_device_info: instance block device information
:param network_info: instance network information
:param network_infos: instance network information
:param disk_info: instance disk information
:param migrate_data: a PowerVMLiveMigrateData object
:param vol_drvs: volume drivers for the attached volumes
@ -149,6 +150,15 @@ class LiveMigrationDest(LiveMigration):
mgmt_task.add_authorized_key(self.drvr.adapter,
migrate_data.public_key)
# For each network info, run the pre-live migration. This tells the
# system what the target vlans will be.
vea_vlan_mappings = {}
for network_info in network_infos:
vif.pre_live_migrate_at_destination(
self.drvr.adapter, self.drvr.host_uuid, self.instance,
network_info, vea_vlan_mappings)
migrate_data.vea_vlan_mappings = vea_vlan_mappings
# For each volume, make sure it's ready to migrate
for vol_drv in vol_drvs:
LOG.info(_LI('Performing pre migration for volume %(volume)s'),
@ -182,12 +192,6 @@ class LiveMigrationDest(LiveMigration):
LOG.debug("Post live migration at destination.",
instance=self.instance)
# Run the post live migration steps at the destination
for network_info in network_infos:
vif.post_live_migrate_at_destination(
self.drvr.adapter, self.drvr.host_uuid, self.instance,
network_info)
# An unbounded dictionary that each volume adapter can use to persist
# data from one call to the next.
mig_vol_stor = {}
@ -205,6 +209,26 @@ class LiveMigrationDest(LiveMigration):
host=self.drvr.host_wrapper.system_name,
name=self.instance.name, volume=vol_drv.volume_id)
def rollback_live_migration_at_destination(
self, context, instance, network_infos, block_device_info,
destroy_disks=True, migrate_data=None):
"""Clean up destination node after a failed live migration.
:param context: security context
:param instance: instance object that was being migrated
:param network_infos: instance network infos
:param block_device_info: instance block device information
:param destroy_disks:
if true, destroy disks at destination during cleanup
:param migrate_data: a LiveMigrateData object
"""
# Clean up any network infos
for network_info in network_infos:
vif.rollback_live_migration_at_destination(
self.drvr.adapter, self.drvr.host_uuid, self.instance,
network_info, migrate_data.vea_vlan_mappings)
def cleanup_volume(self, vol_drv):
"""Cleanup a volume after a failed migration.
@ -308,6 +332,7 @@ class LiveMigrationSrc(LiveMigration):
# The passed in mig data has more info (dest data added), so replace
self.mig_data = migrate_data
# Get the vFC and vSCSI live migration mappings
vol_data = migrate_data.vol_data
vfc_mappings = vol_data.get('vfc_lpm_mappings')
@ -317,6 +342,18 @@ class LiveMigrationSrc(LiveMigration):
if vscsi_mappings is not None:
vscsi_mappings = jsonutils.loads(vscsi_mappings)
# Run the pre-live migration on the network objects
network_infos = self.instance.info_cache.network_info
trunks_to_del = []
for network_info in network_infos:
trunks_to_del.extend(vif.pre_live_migrate_at_source(
self.drvr.adapter, self.drvr.host_uuid, self.instance,
network_info))
# Convert the network mappings into something the API can understand.
vlan_mappings = self._convert_nl_io_mappings(
migrate_data.vea_vlan_mappings)
try:
# Migrate the LPAR!
mig.migrate_lpar(
@ -325,14 +362,27 @@ class LiveMigrationSrc(LiveMigration):
tgt_mgmt_usr=self.mig_data.dest_user_id,
virtual_fc_mappings=vfc_mappings,
virtual_scsi_mappings=vscsi_mappings,
sdn_override=True, vlan_check_override=True)
vlan_mappings=vlan_mappings, sdn_override=True,
vlan_check_override=True)
# Delete the source side network trunk adapters
for trunk_to_del in trunks_to_del:
trunk_to_del.delete()
except Exception:
LOG.error(_LE("Live migration failed."), instance=self.instance)
raise
finally:
LOG.debug("Finished migration.", instance=self.instance)
def _convert_nl_io_mappings(self, mappings):
if not mappings:
return None
resp = []
for mac, value in six.iteritems(mappings):
resp.append("%s/%s" % (util.sanitize_mac_for_api(mac), value))
return resp
def post_live_migration(self, vol_drvs, migrate_data):
"""Post operation of live migration at source host.
@ -362,10 +412,6 @@ class LiveMigrationSrc(LiveMigration):
:param network_infos: instance network information
"""
LOG.debug("Post live migration at source.", instance=self.instance)
for network_info in network_infos:
vif.post_live_migrate_at_source(
self.drvr.adapter, self.drvr.host_uuid, self.instance,
network_info)
def rollback_live_migration(self, context):
"""Roll back a failed migration.

View File

@ -15,15 +15,14 @@
# under the License.
import abc
import logging
import six
from nova import exception
from nova.network import linux_net
from nova.network import model as network_model
from nova import utils
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log
from oslo_serialization import jsonutils
from oslo_utils import importutils
from pypowervm import exceptions as pvm_ex
@ -43,7 +42,8 @@ from nova_powervm.virt.powervm.i18n import _LI
from nova_powervm.virt.powervm.i18n import _LW
from nova_powervm.virt.powervm import vm
LOG = logging.getLogger(__name__)
LOG = log.getLogger(__name__)
SECURE_RMC_VSWITCH = 'MGMTSWITCH'
SECURE_RMC_VLAN = 4094
@ -179,30 +179,57 @@ def unplug(adapter, host_uuid, instance, vif, slot_mgr, cna_w_list=None):
slot_mgr.drop_vnet(vnet_w)
def post_live_migrate_at_destination(adapter, host_uuid, instance, vif):
"""Performs live migrate cleanup on the destination host.
def pre_live_migrate_at_destination(adapter, host_uuid, instance, vif,
vea_vlan_mappings):
"""Performs the pre live migrate on the destination host.
:param adapter: The pypowervm adapter.
:param host_uuid: The host UUID for the PowerVM API.
:param instance: The nova instance object.
:param vif: The virtual interface that was migrated. This may be called
network_info in other portions of the code.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif mac
address, value is the destination's target
hypervisor VLAN.
"""
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
vif_drv.post_live_migrate_at_destination(vif)
vif_drv.pre_live_migrate_at_destination(vif, vea_vlan_mappings)
def post_live_migrate_at_source(adapter, host_uuid, instance, vif):
"""Performs live migrate cleanup on the source host.
def rollback_live_migration_at_destination(adapter, host_uuid, instance, vif,
vea_vlan_mappings):
"""Performs the rollback of the live migrate on the destination host.
:param adapter: The pypowervm adapter.
:param host_uuid: The host UUID for the PowerVM API.
:param instance: The nova instance object.
:param vif: The virtual interface that was migrated. This may be called
network_info in other portions of the code.
:param vif: The virtual interface that is being rolled back. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif mac
address, value is the destination's target
hypervisor VLAN.
"""
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
vif_drv.post_live_migrate_at_source(vif)
vif_drv.rollback_live_migration_at_destination(vif, vea_vlan_mappings)
def pre_live_migrate_at_source(adapter, host_uuid, instance, vif):
"""Performs the pre live migrate on the source host.
This is executed directly before the migration is started on the source
host.
:param adapter: The pypowervm adapter.
:param host_uuid: The host UUID for the PowerVM API.
:param instance: The nova instance object.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:return: The list of TrunkAdapter's on the source that are hosting the
VM's vif. Should only return data if those trunks should be
deleted after the migration.
"""
vif_drv = _build_vif_driver(adapter, host_uuid, instance, vif)
return vif_drv.pre_live_migrate_at_source(vif)
def get_secure_rmc_vswitch(adapter, host_uuid):
@ -333,24 +360,45 @@ class PvmVifDriver(object):
return cna_w
return None
def post_live_migrate_at_destination(self, vif):
"""Performs live migrate cleanup on the destination host.
def pre_live_migrate_at_destination(self, vif, vea_vlan_mappings):
"""Performs the pre live migrate on the destination host.
This is optional, child classes do not need to implement this.
Pre live migrate at destination is invoked before
pre_live_migrate_at_source.
:param vif: The virtual interface that was migrated.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif
mac address, value is the destination's
target hypervisor VLAN.
"""
pass
def post_live_migrate_at_source(self, vif):
"""Performs live migrate cleanup on the source host.
def rollback_live_migrate_at_destination(self, vif, vea_vlan_mappings):
"""Rolls back the pre live migrate on the destination host.
This is optional, child classes do not need to implement this.
:param vif: The virtual interface that was migrated.
:param vif: The virtual interface that was being migrated. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif
mac address, value is the destination's
target hypervisor VLAN.
"""
pass
def pre_live_migrate_at_source(self, vif):
"""Performs the pre live migrate on the source host.
This is executed directly before the migration is started on the source
host.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:return: The list of TrunkAdapter's on the source that are hosting the
VM's vif. Should only return data if those trunks should be
deleted after the migration.
"""
return []
class PvmSeaVifDriver(PvmVifDriver):
"""The PowerVM Shared Ethernet Adapter VIF Driver."""
@ -661,58 +709,102 @@ class PvmOvsVifDriver(PvmLioVifDriver):
# Now delete the client CNA
return super(PvmOvsVifDriver, self).unplug(vif, cna_w_list=cna_w_list)
def post_live_migrate_at_destination(self, vif):
"""Performs live migrate cleanup on the destination host.
def pre_live_migrate_at_destination(self, vif, vea_vlan_mappings):
"""Performs the pre live migrate on the destination host.
This is optional, child classes do not need to implement this.
This method will create the trunk adapter on the destination host,
set its link state up, and attach it to the integration OVS switch.
It also updates the vea_vlan_mappings to indicate which unique
hypervisor VLAN should be used for this VIF for the migration operation
to complete properly.
:param vif: The virtual interface that was migrated.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif
mac address, value is the destination's
target hypervisor VLAN.
"""
# 1) Find a free vlan to use
# 2) Update the migrated CNA to use the new vlan that was found
# and ensure that the CNA is enabled
# 3) Create a trunk adapter on the destination of the migration
# using the same vlan as the CNA
mgmt_wrap = pvm_par.get_this_partition(self.adapter)
dev_name = self.get_trunk_dev_name(vif)
mac = pvm_util.sanitize_mac_for_api(vif['address'])
dev = self.get_trunk_dev_name(vif)
# Get vlan
vswitch_w = pvm_net.VSwitch.search(
self.adapter, parent_type=pvm_ms.System.schema_type,
one_result=True, parent_uuid=self.host_uuid,
name=CONF.powervm.pvm_vswitch_for_novalink_io)
cna = vm.get_cnas(
self.adapter, self.instance, mac=mac, one_result=True)
# Find a specific free VLAN and create the Trunk in a single atomic
# action.
cna_w = pvm_cna.crt_trunk_with_free_vlan(
self.adapter, self.host_uuid, [mgmt_wrap.uuid],
CONF.powervm.pvm_vswitch_for_novalink_io, dev_name=dev)[0]
# Assigns a free vlan (which is returned) to the cna_list
# also enable the cna
cna = pvm_cna.assign_free_vlan(
self.adapter, self.host_uuid, vswitch_w, cna)
# Create a trunk with the vlan_id
trunk_adpt = pvm_net.CNA.bld(
self.adapter, cna.pvid, vswitch_w.related_href, trunk_pri=1,
dev_name=dev_name)
trunk_adpt.create(parent=mgmt_wrap)
utils.execute('ip', 'link', 'set', dev_name, 'up', run_as_root=True)
linux_net.create_ovs_vif_port(vif['network']['bridge'], dev_name,
# Bring the vif up. This signals to neutron that its ready for vif
# plugging
utils.execute('ip', 'link', 'set', dev, 'up', run_as_root=True)
linux_net.create_ovs_vif_port(vif['network']['bridge'], dev,
self.get_ovs_interfaceid(vif),
vif['address'], self.instance.uuid)
@lockutils.synchronized("post_migration_pvm_ovs")
def post_live_migrate_at_source(self, vif):
"""Performs live migrate cleanup on the source host.
# Save this data for the migration command.
vea_vlan_mappings[vif['address']] = cna_w.pvid
LOG.info(_LI("VIF with mac %(mac)s is going on trunk %(dev)s with "
"PVID %(pvid)s"),
{'mac': vif['address'], 'dev': dev, 'pvid': cna_w.pvid},
instance=self.instance)
This is optional, child classes do not need to implement this.
def rollback_live_migrate_at_destination(self, vif, vea_vlan_mappings):
"""Rolls back the pre live migrate on the destination host.
:param vif: The virtual interface that was migrated.
Will delete the TrunkAdapter that pre_live_migrate_at_destination
created with its unique hypervisor VLAN. This uses the
vea_vlan_mappings to provide the information as to what TrunkAdapter
it should remove.
:param vif: The virtual interface that was being migrated. This may be
called network_info in other portions of the code.
:param vea_vlan_mappings: The VEA VLAN mappings. Key is the vif
mac address, value is the destination's
target hypervisor VLAN.
"""
# Deletes orphaned trunks
orphaned_trunks = pvm_cna.find_orphaned_trunks(
self.adapter, CONF.powervm.pvm_vswitch_for_novalink_io)
dev = self.get_trunk_dev_name(vif)
linux_net.delete_ovs_vif_port(vif['network']['bridge'], dev)
for orphan in orphaned_trunks:
orphan.delete()
LOG.warning(_LW("Rolling back the live migrate of VIF with mac "
"%(mac)s."), {'mac': vif['address']},
instance=self.instance)
# We know that we just attached the VIF to the NovaLink VM. Search
# for a trunk adapter with the PVID and vSwitch that we specified
# above. This is guaranteed to be unique.
vlan = vea_vlan_mappings[vif['address']]
vswitch_id = pvm_net.VSwitch.search(
self.adapter, parent_type=pvm_ms.System, one_result=True,
name=CONF.powervm.pvm_vswitch_for_novalink_io).switch_id
# Find the trunk
mgmt_wrap = pvm_par.get_this_partition(self.adapter)
trunk = pvm_net.CNA.search(
self.adapter, parent_type=mgmt_wrap.schema_type,
parent_uuid=mgmt_wrap.uuid, pvid=vlan, vswitch_id=vswitch_id,
one_result=True)
if trunk:
# Delete the peer'd trunk adapter.
LOG.warning(_LW("Deleting target side trunk adapter %(dev)s for "
"rollback operation"), {'dev': trunk.dev_name},
instance=self.instance)
trunk.delete()
def pre_live_migrate_at_source(self, vif):
"""Performs the pre live migrate on the source host.
This is executed directly before the migration is started on the source
host.
:param vif: The virtual interface that will be migrated. This may be
called network_info in other portions of the code.
:return: The list of TrunkAdapter's on the source that are hosting the
VM's vif. Should only return data if those trunks should be
deleted after the migration.
"""
# Right before the migration, we need to find the trunk on the source
# host.
mac = pvm_util.sanitize_mac_for_api(vif['address'])
cna_w = pvm_net.CNA.search(
self.adapter, parent_type=pvm_lpar.LPAR.schema_type,
parent_uuid=vm.get_pvm_uuid(self.instance), one_result=True,
mac=mac)
return pvm_cna.find_trunks(self.adapter, cna_w)