diff --git a/nova_powervm/tests/virt/powervm/disk/test_localdisk.py b/nova_powervm/tests/virt/powervm/disk/test_localdisk.py index c815bf27..b5e74313 100644 --- a/nova_powervm/tests/virt/powervm/disk/test_localdisk.py +++ b/nova_powervm/tests/virt/powervm/disk/test_localdisk.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + import mock from oslo_config import cfg @@ -204,6 +206,41 @@ 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) + inst = mock.Mock() + inst.name = 'Name Of Instance' + inst.uuid = 'd5065c2c-ac43-3fa6-af32-ea84a3960291' + lpar_wrap = mock.Mock() + lpar_wrap.id = 2 + vios1 = pvm_vios.VIOS.wrap(self.vio_to_vg) + vios2 = copy.deepcopy(vios1) + vios1.scsi_mappings[0].backing_storage.name = 'b_Name_Of__d506' + + # Good path + self.apt.read.return_value = vios1.entry + for vdisk, vios in local.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + self.assertEqual('0300025d4a00007a000000014b36d9deaf.1', + vdisk.udid) + self.assertEqual('3443DB77-AED1-47ED-9AA5-3DB9C6CF7089', vios.uuid) + self.assertEqual(1, self.apt.read.call_count) + + # Not found because no storage of that name + self.apt.reset_mock() + self.apt.read.return_value = vios2.entry + for vdisk, vios in local.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + self.fail() + self.assertEqual(1, self.apt.read.call_count) + + # Not found because LPAR ID doesn't match + self.apt.reset_mock() + self.apt.read.return_value = vios1.entry + lpar_wrap.id = 3 + for vdisk, vios in local.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + self.fail() + self.assertEqual(1, self.apt.read.call_count) + class TestLocalDiskFindVG(test.TestCase): """Test in separate class for the static loading of the VG. @@ -246,7 +283,7 @@ class TestLocalDiskFindVG(test.TestCase): storage = ld.LocalStorage({'adapter': self.apt, 'host_uuid': 'host_uuid'}) - # Make sure the uuid's match + # Make sure the uuids match self.assertEqual('d5065c2c-ac43-3fa6-af32-ea84a3960291', storage.vg_uuid) diff --git a/nova_powervm/tests/virt/powervm/disk/test_ssp.py b/nova_powervm/tests/virt/powervm/disk/test_ssp.py index d49501c0..92d37f4e 100644 --- a/nova_powervm/tests/virt/powervm/disk/test_ssp.py +++ b/nova_powervm/tests/virt/powervm/disk/test_ssp.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy + import fixtures import mock from oslo_config import cfg @@ -25,6 +27,7 @@ import pypowervm.entities as pvm_ent from pypowervm.tests.wrappers.util import pvmhttp from pypowervm.wrappers import cluster as pvm_clust 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 ssp @@ -434,3 +437,68 @@ class TestSSPDiskAdapter(test.TestCase): not_same = {'ssp_uuid': 'uuid value not the same'} self.assertFalse( ssp_stor.check_instance_shared_storage_remote('context', not_same)) + + def test_instance_disk_iter(self): + ssp_stor = self._get_ssp_stor() + inst = mock.Mock() + inst.name = 'my-instance-name' + lpar_wrap = mock.Mock() + lpar_wrap.id = 4 + # Build mock VIOS Wrappers as the returns from VIOS.wrap. + # vios1 and vios2 will both have the mapping for client ID 4 and LU + # named boot_my_instance_name. + vios1 = pvm_vios.VIOS.wrap(pvmhttp.load_pvm_resp( + 'fake_vios_ssp_npiv.txt', adapter=self.apt).get_response()) + 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: + vios2.name = 'vios2' + 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' + resp3 = self._bld_resp(entry_or_list=vios3.entry) + + # Test with two VIOSes, both of which contain the mapping + self.apt.read.side_effect = [resp1, resp2] + count = 0 + for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + count += 1 + self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23' + 'fb24756e9713a93f90', lu.udid) + self.assertEqual('vios1_181.68' if count == 1 else 'vios2', + vios.name) + self.assertEqual(2, count) + self.assertEqual(2, self.apt.read.call_count) + + # 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] + for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23' + 'fb24756e9713a93f90', lu.udid) + self.assertEqual('vios1_181.68', vios.name) + break + self.assertEqual(1, self.apt.read.call_count) + + # Now the first VIOS doesn't have the mapping, but the second does + self.apt.reset_mock() + self.apt.read.side_effect = [resp3, resp2] + count = 0 + for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + count += 1 + self.assertEqual('274d7bb790666211e3bc1a00006cae8b01ac18997ab9bc23' + 'fb24756e9713a93f90', lu.udid) + self.assertEqual('vios2', vios.name) + self.assertEqual(1, count) + self.assertEqual(2, self.apt.read.call_count) + + # No hits + self.apt.reset_mock() + self.apt.read.side_effect = [resp3, resp3] + for lu, vios in ssp_stor.instance_disk_iter(inst, lpar_wrap=lpar_wrap): + self.fail() + self.assertEqual(2, self.apt.read.call_count) diff --git a/nova_powervm/virt/powervm/disk/driver.py b/nova_powervm/virt/powervm/disk/driver.py index 597947a8..f9faa679 100644 --- a/nova_powervm/virt/powervm/disk/driver.py +++ b/nova_powervm/virt/powervm/disk/driver.py @@ -21,7 +21,11 @@ from oslo_utils import units import six 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 vm class DiskType(object): @@ -66,6 +70,57 @@ class DiskAdapter(object): self._connection = connection self.image_api = image.API() + @property + def vios_uuids(self): + """List the UUIDs of the Virtual I/O Servers hosting the storage.""" + raise NotImplementedError() + + def disk_match_func(self, disk_type, instance): + """Return a matching function to locate the disk for an instance. + + :param disk_type: One of the DiskType enum values. + :param instance: The instance whose disk is to be found. + :return: Callable suitable for the match_func parameter of the + pypowervm.tasks.scsi_mapper.find_maps method, with the + following specification: + def match_func(storage_elem) + param storage_elem: A backing storage element wrapper (VOpt, + VDisk, PV, or LU) to be analyzed. + return: True if the storage_elem's mapping should be included; + False otherwise. + """ + raise NotImplementedError() + + def instance_disk_iter(self, instance, disk_type=DiskType.BOOT, + lpar_wrap=None): + """Return the instance's storage element wrapper of the specified type. + + :param instance: nova.objects.instance.Instance object owning the + requested disk. + :param disk_type: The type of disk to find, one of the DiskType enum + values. + :param lpar_wrap: pypowervm.wrappers.logical_partition.LPAR + corresponding to the instance. If not specified, it + will be retrieved; i.e. specify this parameter to + save on REST calls. + :return: Iterator of tuples of (storage_elem, VIOS), where storage_elem + is a storage element wrapper (pypowervm.wrappers.storage.VOpt, + VDisk, PV, or LU) associated with the instance; and VIOS is + the wrapper of the Virtual I/O Server owning that storage + element. + """ + if lpar_wrap is None: + lpar_wrap = vm.get_instance_wrapper(self.adapter, instance, + self.host_uuid) + match_func = self.disk_match_func(disk_type, instance) + for vios_uuid in self.vios_uuids: + vios_wrap = pvm_vios.VIOS.wrap(self.adapter.read( + pvm_vios.VIOS.schema_type, root_id=vios_uuid, + xag=pvm_vios.VIOS.xags.SCSI_MAPPING)) + for scsi_map in tsk_map.find_maps( + vios_wrap.scsi_mappings, lpar_wrap.id, match_func): + yield scsi_map.backing_storage, vios_wrap + @property def capacity(self): """Capacity of the storage in gigabytes diff --git a/nova_powervm/virt/powervm/disk/localdisk.py b/nova_powervm/virt/powervm/disk/localdisk.py index 2636d857..c8c5456f 100644 --- a/nova_powervm/virt/powervm/disk/localdisk.py +++ b/nova_powervm/virt/powervm/disk/localdisk.py @@ -66,10 +66,35 @@ class LocalStorage(disk_dvr.DiskAdapter): # Query to get the Volume Group UUID self.vg_name = CONF.volume_group_name - self.vios_uuid, self.vg_uuid = self._get_vg_uuid(self.vg_name) + self._vios_uuid, self.vg_uuid = self._get_vg_uuid(self.vg_name) LOG.info(_LI("Local Storage driver initialized: volume group: '%s'"), self.vg_name) + @property + def vios_uuids(self): + """List the UUIDs of the Virtual I/O Servers hosting the storage. + + For localdisk, there's only one. + """ + return [self._vios_uuid] + + def disk_match_func(self, disk_type, instance): + """Return a matching function to locate the disk for an instance. + + :param disk_type: One of the DiskType enum values. + :param instance: The instance whose disk is to be found. + :return: Callable suitable for the match_func parameter of the + pypowervm.tasks.scsi_mapper.find_maps method, with the + following specification: + def match_func(storage_elem) + param storage_elem: A backing storage element wrapper (VOpt, + VDisk, PV, or LU) to be analyzed. + return: True if the storage_elem's mapping should be included; + False otherwise. + """ + disk_name = self._get_disk_name(disk_type, instance, short=True) + return tsk_map.gen_match_func(pvm_stg.VDisk, names=[disk_name]) + @property def capacity(self): """Capacity of the storage in gigabytes.""" @@ -131,7 +156,7 @@ class LocalStorage(disk_dvr.DiskAdapter): disconnected from the I/O Server and VM. """ partition_id = vm.get_vm_id(self.adapter, lpar_uuid) - return tsk_map.remove_vdisk_mapping(self.adapter, self.vios_uuid, + return tsk_map.remove_vdisk_mapping(self.adapter, self._vios_uuid, partition_id, disk_prefixes=disk_type) @@ -164,7 +189,7 @@ class LocalStorage(disk_dvr.DiskAdapter): # If the image is bigger than disk, API should make the disk big # enough to support the image (up to 1 Gb boundary). vdisk, f_wrap = tsk_stg.upload_new_vdisk( - self.adapter, self.vios_uuid, self.vg_uuid, stream, vol_name, + self.adapter, self._vios_uuid, self.vg_uuid, stream, vol_name, image['size'], d_size=disk_bytes) return vdisk @@ -180,7 +205,7 @@ class LocalStorage(disk_dvr.DiskAdapter): :param: lpar_uuid: The pypowervm UUID that corresponds to the VM. """ # Add the mapping to the VIOS - tsk_map.add_vscsi_mapping(self.host_uuid, self.vios_uuid, lpar_uuid, + tsk_map.add_vscsi_mapping(self.host_uuid, self._vios_uuid, lpar_uuid, disk_info) def extend_disk(self, context, instance, disk_info, size): @@ -259,7 +284,7 @@ class LocalStorage(disk_dvr.DiskAdapter): def _get_vg(self): vg_rsp = self.adapter.read( - pvm_vios.VIOS.schema_type, root_id=self.vios_uuid, + pvm_vios.VIOS.schema_type, root_id=self._vios_uuid, child_type=pvm_stg.VG.schema_type, child_id=self.vg_uuid) return vg_rsp diff --git a/nova_powervm/virt/powervm/disk/ssp.py b/nova_powervm/virt/powervm/disk/ssp.py index 916fb1aa..a684dbe4 100644 --- a/nova_powervm/virt/powervm/disk/ssp.py +++ b/nova_powervm/virt/powervm/disk/ssp.py @@ -389,3 +389,20 @@ class SSPDiskAdapter(disk_drv.DiskAdapter): :return: A single VIOS UUID string. """ return random.choice(self.vios_uuids) + + def disk_match_func(self, disk_type, instance): + """Return a matching function to locate the disk for an instance. + + :param disk_type: One of the DiskType enum values. + :param instance: The instance whose disk is to be found. + :return: Callable suitable for the match_func parameter of the + pypowervm.tasks.scsi_mapper.find_maps method, with the + following specification: + def match_func(storage_elem) + param storage_elem: A backing storage element wrapper (VOpt, + VDisk, PV, or LU) to be analyzed. + return: True if the storage_elem's mapping should be included; + False otherwise. + """ + disk_name = self._get_disk_name(disk_type, instance) + return tsk_map.gen_match_func(pvm_stg.LU, names=[disk_name])