(Un)map instance boot disk to Management Partition

In the disk drivers, provide the ability to map and unmap an instance's
boot disk to/from the management partition.

Change-Id: I992100da5618bb804da547e62308b83114819b0d
This commit is contained in:
Eric Fried 2015-05-20 13:45:44 -05:00
parent 85f1e763a3
commit 15e604a8a5
5 changed files with 296 additions and 10 deletions

View File

@ -206,9 +206,7 @@ class TestLocalDisk(test.TestCase):
self.assertEqual(1, resp.update.call_count)
self.assertEqual(vdisk.capacity, 1000)
@mock.patch('pypowervm.wrappers.storage.VG')
def test_instance_disk_iter(self, mock_vg):
local = self.get_ls(self.apt)
def _bld_mocks_for_instance_disk(self):
inst = mock.Mock()
inst.name = 'Name Of Instance'
inst.uuid = 'd5065c2c-ac43-3fa6-af32-ea84a3960291'
@ -217,6 +215,12 @@ class TestLocalDisk(test.TestCase):
vios1 = pvm_vios.VIOS.wrap(self.vio_to_vg)
vios2 = copy.deepcopy(vios1)
vios1.scsi_mappings[0].backing_storage.name = 'b_Name_Of__d506'
return inst, lpar_wrap, vios1, vios2
@mock.patch('pypowervm.wrappers.storage.VG')
def test_instance_disk_iter(self, mock_vg):
local = self.get_ls(self.apt)
inst, lpar_wrap, vios1, vios2 = self._bld_mocks_for_instance_disk()
# Good path
self.apt.read.return_value = vios1.entry
@ -241,6 +245,81 @@ class TestLocalDisk(test.TestCase):
self.fail()
self.assertEqual(1, self.apt.read.call_count)
def _mp_wrap_mock(self):
mp_wrap = mock.Mock()
mp_wrap.name = 'ManagementPartition'
mp_wrap.id = 'mp_id'
mp_wrap.uuid = 'mp_uuid'
return mp_wrap
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper')
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping')
def test_connect_instance_disk_to_mgmt_partition(self, mock_add, mock_lw):
local = self.get_ls(self.apt)
inst, lpar_wrap, vios1, vios2 = self._bld_mocks_for_instance_disk()
mp_wrap = self._mp_wrap_mock()
mock_lw.return_value = lpar_wrap
# Good path
self.apt.read.return_value = vios1.entry
vdisk, vios, mpw = local.connect_instance_disk_to_mgmt(inst,
mp_wrap=mp_wrap)
self.assertEqual('0300025d4a00007a000000014b36d9deaf.1', vdisk.udid)
self.assertIs(mp_wrap, mpw)
self.assertIs(vios1.entry, vios.entry)
self.assertEqual(1, mock_add.call_count)
mock_add.assert_called_with('host_uuid', vios1.uuid, 'mp_uuid', vdisk)
# Not found
mock_add.reset_mock()
self.apt.read.return_value = vios2.entry
self.assertRaises(
disk_dvr.InstanceDiskMappingFailed,
local.connect_instance_disk_to_mgmt, inst, mp_wrap=mp_wrap)
self.assertEqual(0, mock_add.call_count)
# add_vscsi_mapping raises. Show-stopper since only one VIOS.
mock_add.reset_mock()
self.apt.read.return_value = vios1.entry
mock_add.side_effect = Exception("mapping failed")
self.assertRaises(
disk_dvr.InstanceDiskMappingFailed,
local.connect_instance_disk_to_mgmt, inst, mp_wrap=mp_wrap)
self.assertEqual(1, mock_add.call_count)
@mock.patch('pypowervm.tasks.scsi_mapper.remove_vdisk_mapping')
def test_disconnect_disk_from_mgmt_partition(self, mock_rm_vdisk_map):
local = self.get_ls(self.apt)
mp_wrap = self._mp_wrap_mock()
local.disconnect_disk_from_mgmt('vios_uuid', 'disk_name', mp_wrap)
mock_rm_vdisk_map.assert_called_with(local.adapter, 'vios_uuid',
'mp_id', disk_names=['disk_name'])
@mock.patch('nova_powervm.virt.powervm.vm.get_mgmt_partition')
@mock.patch('nova_powervm.virt.powervm.disk.localdisk.LocalStorage.'
'instance_disk_iter')
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping')
@mock.patch('pypowervm.tasks.scsi_mapper.remove_vdisk_mapping')
def test_discover_mgmt_partition(self, mock_rm_vdisk, mock_add_map,
mock_inst_disk_iter, mock_get_mgmt):
"""Ensure connect/disconnect will discover the mgmt partition."""
local = self.get_ls(self.apt)
mp_wrap = self._mp_wrap_mock()
inst = mock.Mock()
mock_inst_disk_iter.return_value = [(mock.Mock(), mock.Mock())]
local.connect_instance_disk_to_mgmt(inst, mp_wrap=mp_wrap)
self.assertFalse(mock_get_mgmt.called)
local.connect_instance_disk_to_mgmt(inst)
self.assertTrue(mock_get_mgmt.called)
mock_get_mgmt.reset_mock()
local.disconnect_disk_from_mgmt('vios_uuid', 'disk_name',
mp_wrap=mp_wrap)
self.assertFalse(mock_get_mgmt.called)
local.disconnect_disk_from_mgmt('vios_uuid', 'disk_name')
self.assertTrue(mock_get_mgmt.called)
class TestLocalDiskFindVG(test.TestCase):
"""Test in separate class for the static loading of the VG.

View File

@ -30,6 +30,7 @@ from pypowervm.wrappers import storage as pvm_stg
from pypowervm.wrappers import virtual_io_server as pvm_vios
from nova_powervm.tests.virt.powervm import fixtures as fx
from nova_powervm.virt.powervm.disk import driver
from nova_powervm.virt.powervm.disk import ssp
@ -438,8 +439,7 @@ class TestSSPDiskAdapter(test.TestCase):
self.assertFalse(
ssp_stor.check_instance_shared_storage_remote('context', not_same))
def test_instance_disk_iter(self):
ssp_stor = self._get_ssp_stor()
def _bld_mocks_for_instance_disk(self):
inst = mock.Mock()
inst.name = 'my-instance-name'
lpar_wrap = mock.Mock()
@ -452,17 +452,24 @@ class TestSSPDiskAdapter(test.TestCase):
vios1.scsi_mappings[3].backing_storage._name('boot_my_instance_name')
resp1 = self._bld_resp(entry_or_list=vios1.entry)
vios2 = copy.deepcopy(vios1)
# Rename vios2 so we can tell the difference:
# Change name and UUID so we can tell the difference:
vios2.name = 'vios2'
vios2.entry.properties['id'] = '7C6475B3-73A5-B9FF-4799-B23B20292951'
resp2 = self._bld_resp(entry_or_list=vios2.entry)
# vios3 will not have the mapping
vios3 = pvm_vios.VIOS.wrap(pvmhttp.load_pvm_resp(
'fake_vios_ssp_npiv.txt', adapter=self.apt).get_response())
vios3.name = 'vios3'
vios3.entry.properties['id'] = 'B9FF4799-B23B-2029-2951-7C6475B373A5'
resp3 = self._bld_resp(entry_or_list=vios3.entry)
return inst, lpar_wrap, resp1, resp2, resp3
def test_instance_disk_iter(self):
ssp_stor = self._get_ssp_stor()
inst, lpar_wrap, rsp1, rsp2, rsp3 = self._bld_mocks_for_instance_disk()
# Test with two VIOSes, both of which contain the mapping
self.apt.read.side_effect = [resp1, resp2]
self.apt.read.side_effect = [rsp1, rsp2]
count = 0
for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap):
count += 1
@ -476,7 +483,7 @@ class TestSSPDiskAdapter(test.TestCase):
# Same, but prove that breaking out of the loop early avoids the second
# Adapter.read call
self.apt.reset_mock()
self.apt.read.side_effect = [resp1, resp2]
self.apt.read.side_effect = [rsp1, rsp2]
for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap):
self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23'
'fb24756e9713a93f90', lu.udid)
@ -486,7 +493,7 @@ class TestSSPDiskAdapter(test.TestCase):
# Now the first VIOS doesn't have the mapping, but the second does
self.apt.reset_mock()
self.apt.read.side_effect = [resp3, resp2]
self.apt.read.side_effect = [rsp3, rsp2]
count = 0
for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap):
count += 1
@ -498,7 +505,99 @@ class TestSSPDiskAdapter(test.TestCase):
# No hits
self.apt.reset_mock()
self.apt.read.side_effect = [resp3, resp3]
self.apt.read.side_effect = [rsp3, rsp3]
for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap):
self.fail()
self.assertEqual(2, self.apt.read.call_count)
def _mp_wrap_mock(self):
mp_wrap = mock.Mock()
mp_wrap.name = 'ManagementPartition'
mp_wrap.id = 'mp_id'
mp_wrap.uuid = 'mp_uuid'
return mp_wrap
@mock.patch('nova_powervm.virt.powervm.vm.get_instance_wrapper')
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping')
def test_connect_instance_disk_to_mgmt(self, mock_add, mock_lw):
ssp_stor = self._get_ssp_stor()
inst, lpar_wrap, rsp1, rsp2, rsp3 = self._bld_mocks_for_instance_disk()
mp_wrap = self._mp_wrap_mock()
mock_lw.return_value = lpar_wrap
# Test with two VIOSes, both of which contain the mapping
self.apt.read.side_effect = [rsp1, rsp2]
lu, vios, mpw = ssp_stor.connect_instance_disk_to_mgmt(inst,
mp_wrap=mp_wrap)
self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23'
'fb24756e9713a93f90', lu.udid)
self.assertIs(mp_wrap, mpw)
# Should hit on the first VIOS
self.assertIs(rsp1.entry, vios.entry)
self.assertEqual(1, mock_add.call_count)
mock_add.assert_called_with('67dca605-3923-34da-bd8f-26a378fc817f',
'75B373A5-B9FF-4799-B23B-202929517C64',
'mp_uuid', lu)
# Now the first VIOS doesn't have the mapping, but the second does
mock_add.reset_mock()
self.apt.read.side_effect = [rsp3, rsp2]
lu, vios, mpw = ssp_stor.connect_instance_disk_to_mgmt(inst,
mp_wrap=mp_wrap)
self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23'
'fb24756e9713a93f90', lu.udid)
self.assertIs(mp_wrap, mpw)
# Should hit on the second VIOS
self.assertIs(rsp2.entry, vios.entry)
self.assertEqual(1, mock_add.call_count)
mock_add.assert_called_with('67dca605-3923-34da-bd8f-26a378fc817f',
'7C6475B3-73A5-B9FF-4799-B23B20292951',
'mp_uuid', lu)
# No hits
mock_add.reset_mock()
self.apt.read.side_effect = [rsp3, rsp3]
self.assertRaises(
driver.InstanceDiskMappingFailed,
ssp_stor.connect_instance_disk_to_mgmt, inst, mp_wrap=mp_wrap)
self.assertEqual(0, mock_add.call_count)
# First add_vscsi_mapping call raises
self.apt.read.side_effect = [rsp1, rsp2]
mock_add.side_effect = [Exception("mapping failed"), None]
# Should hit on the second VIOS
self.assertIs(rsp2.entry, vios.entry)
@mock.patch('pypowervm.tasks.scsi_mapper.remove_lu_mapping')
def test_disconnect_disk_from_mgmt(self, mock_rm_lu_map):
ssp_stor = self._get_ssp_stor()
mp_wrap = self._mp_wrap_mock()
ssp_stor.disconnect_disk_from_mgmt('vios_uuid', 'disk_name',
mp_wrap=mp_wrap)
mock_rm_lu_map.assert_called_with(ssp_stor.adapter, 'vios_uuid',
'mp_id', disk_names=['disk_name'])
@mock.patch('nova_powervm.virt.powervm.vm.get_mgmt_partition')
@mock.patch('nova_powervm.virt.powervm.disk.ssp.SSPDiskAdapter.'
'instance_disk_iter')
@mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping')
@mock.patch('pypowervm.tasks.scsi_mapper.remove_lu_mapping')
def test_discover_mgmt_partition(self, mock_rm_lu, mock_add_map,
mock_inst_disk_iter, mock_get_mgmt):
"""Ensure connect/disconnect will discover the mgmt partition."""
ssp_stor = self._get_ssp_stor()
mp_wrap = self._mp_wrap_mock()
inst = mock.Mock()
mock_inst_disk_iter.return_value = [(mock.Mock(), mock.Mock())]
ssp_stor.connect_instance_disk_to_mgmt(inst, mp_wrap=mp_wrap)
self.assertFalse(mock_get_mgmt.called)
ssp_stor.connect_instance_disk_to_mgmt(inst)
self.assertTrue(mock_get_mgmt.called)
mock_get_mgmt.reset_mock()
ssp_stor.disconnect_disk_from_mgmt('vios_uuid', 'disk_name',
mp_wrap=mp_wrap)
self.assertFalse(mock_get_mgmt.called)
ssp_stor.disconnect_disk_from_mgmt('vios_uuid', 'disk_name')
self.assertTrue(mock_get_mgmt.called)

View File

@ -17,16 +17,21 @@
import abc
import oslo_log.log as logging
from oslo_utils import units
import six
from nova.i18n import _, _LW, _LI
from nova import image
import pypowervm.tasks.scsi_mapper as tsk_map
import pypowervm.util as pvm_util
import pypowervm.wrappers.virtual_io_server as pvm_vios
from nova_powervm.virt.powervm import disk
from nova_powervm.virt.powervm import vm
LOG = logging.getLogger(__name__)
class DiskType(object):
BOOT = 'boot'
@ -34,6 +39,12 @@ class DiskType(object):
IMAGE = 'image'
class InstanceDiskMappingFailed(disk.AbstractDiskException):
msg_fmt = _("Failed to map boot disk of instance %(instance_name)s to "
"management partition %(mp_name)s from any Virtual I/O "
"Server.")
class IterableToFileAdapter(object):
"""A degenerate file-like so that an iterable could be read like a file.
@ -121,6 +132,61 @@ class DiskAdapter(object):
vios_wrap.scsi_mappings, lpar_wrap.id, match_func):
yield scsi_map.backing_storage, vios_wrap
def connect_instance_disk_to_mgmt(self, instance, mp_wrap=None):
"""Connect an instance's boot disk to the management partition.
:param instance: The instance whose boot disk is to be mapped.
:param mp_wrap: The LPAR EntryWrapper representing the management
partition. If not specified, it will be looked up.
:return stg_elem: The storage element (LU, VDisk, etc.) that was mapped
:return vios: The EntryWrapper of the VIOS from which the mapping was
made.
:return mp_wrap: The LPAR EntryWrapper representing the management
partition. Same as the mp_wrap parameter, if it was
supplied.
:raise InstanceDiskMappingFailed: If the mapping could not be done.
"""
if mp_wrap is None:
mp_wrap = vm.get_mgmt_partition(self.adapter)
msg_args = {'instance_name': instance.name,
'mp_name': mp_wrap.name}
for stg_elem, vios in self.instance_disk_iter(instance):
msg_args['disk_name'] = stg_elem.name
msg_args['vios_name'] = vios.name
LOG.debug("Mapping boot disk %(disk_name)s of instance "
"%(instance_name)s to management partition %(mp_name)s "
"from Virtual I/O Server %(vios_name)s.", msg_args)
try:
tsk_map.add_vscsi_mapping(self.host_uuid, vios.uuid,
mp_wrap.uuid, stg_elem)
# If that worked, we're done. Let the caller know where the
# mapping happened from.
LOG.info(_LI(
"Mapped boot disk %(disk_name)s of instance "
"%(instance_name)s to management partition %(mp_name)s "
"from Virtual I/O Server %(vios_name)s."), msg_args)
return stg_elem, vios, mp_wrap
except Exception as e:
msg_args['exc'] = e
LOG.warn(_LW("Failed to map boot disk %(disk_name)s of "
"instance %(instance_name)s to management "
"partition %(mp_name)s from Virtual I/O Server "
"%(vios_name)s: %(exc)s"), msg_args)
# Try the next hit, if available.
# We either didn't find the boot dev, or failed all attempts to map it.
raise InstanceDiskMappingFailed(**msg_args)
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name, mp_wrap=None):
"""Disconnect a disk from the management partition.
:param vios_uuid: The UUID of the Virtual I/O Server serving the
mapping.
:param disk_name: The name of the disk to unmap.
:param mp_wrap: The LPAR EntryWrapper representing the management
partition. If not specified, it will be looked up.
"""
raise NotImplementedError()
@property
def capacity(self):
"""Capacity of the storage in gigabytes

View File

@ -160,6 +160,27 @@ class LocalStorage(disk_dvr.DiskAdapter):
partition_id,
disk_prefixes=disk_type)
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name, mp_wrap=None):
"""Disconnect a disk from the management partition.
:param vios_uuid: The UUID of the Virtual I/O Server serving the
mapping.
:param disk_name: The name of the disk to unmap.
:param mp_wrap: The pypowervm LPAR EntryWrapper representing the
management partition. If not specified, it will be
looked up.
"""
if mp_wrap is None:
mp_wrap = vm.get_mgmt_partition(self.adapter)
tsk_map.remove_vdisk_mapping(self.adapter, vios_uuid, mp_wrap.id,
disk_names=[disk_name])
LOG.info(_LI(
"Unmapped boot disk %(disk_name)s from management partition "
"%(mp_name)s from Virtual I/O Server %(vios_name)s."), {
'disk_name': disk_name,
'mp_name': mp_wrap.name,
'vios_name': vios_uuid})
def create_disk_from_image(self, context, instance, image, disk_size,
image_type=disk_dvr.DiskType.BOOT):
"""Creates a disk and copies the specified image to it.

View File

@ -129,6 +129,27 @@ class SSPDiskAdapter(disk_drv.DiskAdapter):
lu_set.add(lu)
return list(lu_set)
def disconnect_disk_from_mgmt(self, vios_uuid, disk_name, mp_wrap=None):
"""Disconnect a disk from the management partition.
:param vios_uuid: The UUID of the Virtual I/O Server serving the
mapping.
:param disk_name: The name of the disk to unmap.
:param mp_wrap: The pypowervm LPAR EntryWrapper representing the
management partition. If not specified, it will be
looked up.
"""
if mp_wrap is None:
mp_wrap = vm.get_mgmt_partition(self.adapter)
tsk_map.remove_lu_mapping(self.adapter, vios_uuid, mp_wrap.id,
disk_names=[disk_name])
LOG.info(_LI(
"Unmapped boot disk %(disk_name)s from management partition "
"%(mp_name)s from Virtual I/O Server %(vios_uuid)s."), {
'disk_name': disk_name,
'mp_name': mp_wrap.name,
'vios_uuid': vios_uuid})
def delete_disks(self, context, instance, storage_elems):
"""Removes the disks specified by the mappings.