(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:
parent
85f1e763a3
commit
15e604a8a5
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue