PowerVM Driver: Localdisk
Add PowerVM Driver support for local ephemeral disk. Change-Id: I9251287451bc2f800ef4a230c3c01598f37b5ad3 Blueprint: powervm-localdisk
This commit is contained in:
		@@ -674,6 +674,10 @@ driver-impl-ironic=missing
 | 
			
		||||
driver-impl-libvirt-vz-vm=complete
 | 
			
		||||
driver-impl-libvirt-vz-ct=complete
 | 
			
		||||
driver-impl-powervm=complete
 | 
			
		||||
driver-notes-powervm=When using the localdisk disk driver, snapshot is only
 | 
			
		||||
  supported if I/O is being hosted by the management partition. If hosting I/O
 | 
			
		||||
  on traditional VIOS, we are limited by the fact that a VSCSI device can't be
 | 
			
		||||
  mapped to two partitions (the VIOS and the management) at once.
 | 
			
		||||
 | 
			
		||||
[operation.suspend]
 | 
			
		||||
title=Suspend instance
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,4 @@
 | 
			
		||||
# Copyright 2018 IBM Corporation
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
@@ -34,8 +33,26 @@ powervm_opts = [
 | 
			
		||||
             compute power given to each vCPU. E.g. A value of 1.0 means a
 | 
			
		||||
             whole physical processor, whereas 0.05 means 1/20th of a physical
 | 
			
		||||
             processor.
 | 
			
		||||
             """
 | 
			
		||||
    ),
 | 
			
		||||
             """),
 | 
			
		||||
    cfg.StrOpt('disk_driver',
 | 
			
		||||
               choices=['localdisk', 'ssp'], ignore_case=True,
 | 
			
		||||
               default='localdisk',
 | 
			
		||||
               help="""The disk driver to use for PowerVM disks. PowerVM
 | 
			
		||||
                    provides support for localdisk and PowerVM Shared Storage
 | 
			
		||||
                    Pool disk drivers.
 | 
			
		||||
 | 
			
		||||
                    Related options:
 | 
			
		||||
 | 
			
		||||
                    * volume_group_name - required when using localdisk
 | 
			
		||||
 | 
			
		||||
                    """),
 | 
			
		||||
    cfg.StrOpt('volume_group_name',
 | 
			
		||||
               default='',
 | 
			
		||||
               help='Volume Group to use for block device operations. If '
 | 
			
		||||
                    'disk_driver is localdisk, then this attribute must be '
 | 
			
		||||
                    'specified. It is strongly recommended NOT to use '
 | 
			
		||||
                    'rootvg since that is used by the management partition '
 | 
			
		||||
                    'and filling it will cause failures.'),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2269,3 +2269,8 @@ class DeviceDeletionException(NovaException):
 | 
			
		||||
    msg_fmt = _("Device %(devpath)s is still present on the management "
 | 
			
		||||
                "partition after attempting to delete it. Polled %(polls)d "
 | 
			
		||||
                "times over %(timeout)d seconds.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OptRequiredIfOtherOptValue(NovaException):
 | 
			
		||||
    msg_fmt = _("The %(then_opt)s option is required if %(if_opt)s is "
 | 
			
		||||
                "specified as '%(if_value)s'.")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										312
									
								
								nova/tests/unit/virt/powervm/disk/test_localdisk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										312
									
								
								nova/tests/unit/virt/powervm/disk/test_localdisk.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,312 @@
 | 
			
		||||
# Copyright 2015, 2018 IBM Corp.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import fixtures
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova import test
 | 
			
		||||
from pypowervm import const as pvm_const
 | 
			
		||||
from pypowervm.tasks import storage as tsk_stg
 | 
			
		||||
from pypowervm.utils import transaction as pvm_tx
 | 
			
		||||
from pypowervm.wrappers import storage as pvm_stg
 | 
			
		||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
 | 
			
		||||
 | 
			
		||||
from nova.tests import uuidsentinel as uuids
 | 
			
		||||
from nova.virt.powervm.disk import driver as disk_dvr
 | 
			
		||||
from nova.virt.powervm.disk import localdisk
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestLocalDisk(test.NoDBTestCase):
 | 
			
		||||
    """Unit Tests for the LocalDisk storage driver."""
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(TestLocalDisk, self).setUp()
 | 
			
		||||
        self.adpt = mock.Mock()
 | 
			
		||||
 | 
			
		||||
        # The mock VIOS needs to have scsi_mappings as a list.  Internals are
 | 
			
		||||
        # set by individual test cases as needed.
 | 
			
		||||
        smaps = [mock.Mock()]
 | 
			
		||||
        self.vio_wrap = mock.create_autospec(
 | 
			
		||||
            pvm_vios.VIOS, instance=True, scsi_mappings=smaps,
 | 
			
		||||
            uuid='vios-uuid')
 | 
			
		||||
 | 
			
		||||
        # Return the mgmt uuid.
 | 
			
		||||
        self.mgmt_uuid = self.useFixture(fixtures.MockPatch(
 | 
			
		||||
            'nova.virt.powervm.mgmt.mgmt_uuid', autospec=True)).mock
 | 
			
		||||
        self.mgmt_uuid.return_value = 'mgmt_uuid'
 | 
			
		||||
 | 
			
		||||
        self.pvm_uuid = self.useFixture(fixtures.MockPatch(
 | 
			
		||||
            'nova.virt.powervm.vm.get_pvm_uuid')).mock
 | 
			
		||||
        self.pvm_uuid.return_value = 'pvm_uuid'
 | 
			
		||||
 | 
			
		||||
        # Set up for the mocks for the disk adapter.
 | 
			
		||||
        self.mock_find_vg = self.useFixture(fixtures.MockPatch(
 | 
			
		||||
            'pypowervm.tasks.storage.find_vg', autospec=True)).mock
 | 
			
		||||
        self.vg_uuid = uuids.vg_uuid
 | 
			
		||||
        self.vg = mock.Mock(spec=pvm_stg.VG, uuid=self.vg_uuid)
 | 
			
		||||
        self.mock_find_vg.return_value = (self.vio_wrap, self.vg)
 | 
			
		||||
 | 
			
		||||
        self.flags(volume_group_name='fakevg', group='powervm')
 | 
			
		||||
 | 
			
		||||
        # Mock the feed tasks.
 | 
			
		||||
        self.mock_afs = self.useFixture(fixtures.MockPatch(
 | 
			
		||||
            'pypowervm.utils.transaction.FeedTask.add_functor_subtask',
 | 
			
		||||
            autospec=True)).mock
 | 
			
		||||
        self.mock_wtsk = mock.create_autospec(
 | 
			
		||||
            pvm_tx.WrapperTask, instance=True)
 | 
			
		||||
        self.mock_wtsk.configure_mock(wrapper=self.vio_wrap)
 | 
			
		||||
        self.mock_ftsk = mock.create_autospec(pvm_tx.FeedTask, instance=True)
 | 
			
		||||
        self.mock_ftsk.configure_mock(
 | 
			
		||||
            wrapper_tasks={'vios-uuid': self.mock_wtsk})
 | 
			
		||||
 | 
			
		||||
        # Create the adapter.
 | 
			
		||||
        self.ld_adpt = localdisk.LocalStorage(self.adpt, 'host_uuid')
 | 
			
		||||
 | 
			
		||||
    def test_init(self):
 | 
			
		||||
        # Localdisk adapter already initialized in setUp()
 | 
			
		||||
        # From super __init__()
 | 
			
		||||
        self.assertEqual(self.adpt, self.ld_adpt._adapter)
 | 
			
		||||
        self.assertEqual('host_uuid', self.ld_adpt._host_uuid)
 | 
			
		||||
        self.assertEqual('mgmt_uuid', self.ld_adpt.mp_uuid)
 | 
			
		||||
 | 
			
		||||
        # From LocalStorage __init__()
 | 
			
		||||
        self.assertEqual('fakevg', self.ld_adpt.vg_name)
 | 
			
		||||
        self.mock_find_vg.assert_called_once_with(self.adpt, 'fakevg')
 | 
			
		||||
        self.assertEqual('vios-uuid', self.ld_adpt._vios_uuid)
 | 
			
		||||
        self.assertEqual(self.vg_uuid, self.ld_adpt.vg_uuid)
 | 
			
		||||
        self.assertFalse(self.ld_adpt.capabilities['shared_storage'])
 | 
			
		||||
        self.assertFalse(self.ld_adpt.capabilities['has_imagecache'])
 | 
			
		||||
        self.assertFalse(self.ld_adpt.capabilities['snapshot'])
 | 
			
		||||
 | 
			
		||||
        # Assert snapshot capability is true if hosting I/O on mgmt partition.
 | 
			
		||||
        self.mgmt_uuid.return_value = 'vios-uuid'
 | 
			
		||||
        self.ld_adpt = localdisk.LocalStorage(self.adpt, 'host_uuid')
 | 
			
		||||
        self.assertTrue(self.ld_adpt.capabilities['snapshot'])
 | 
			
		||||
 | 
			
		||||
        # Assert volume_group_name is required.
 | 
			
		||||
        self.flags(volume_group_name=None, group='powervm')
 | 
			
		||||
        self.assertRaises(exception.OptRequiredIfOtherOptValue,
 | 
			
		||||
                          localdisk.LocalStorage, self.adpt, 'host_uuid')
 | 
			
		||||
 | 
			
		||||
    def test_vios_uuids(self):
 | 
			
		||||
        self.assertEqual(['vios-uuid'], self.ld_adpt._vios_uuids)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_disk_name')
 | 
			
		||||
    def test_disk_match_func(self, mock_disk_name, mock_gen_match):
 | 
			
		||||
        mock_disk_name.return_value = 'disk_name'
 | 
			
		||||
        func = self.ld_adpt._disk_match_func('disk_type', 'instance')
 | 
			
		||||
        mock_disk_name.assert_called_once_with(
 | 
			
		||||
            'disk_type', 'instance', short=True)
 | 
			
		||||
        mock_gen_match.assert_called_once_with(
 | 
			
		||||
            pvm_stg.VDisk, names=['disk_name'])
 | 
			
		||||
        self.assertEqual(mock_gen_match.return_value, func)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._get_vg_wrap')
 | 
			
		||||
    def test_capacity(self, mock_vg):
 | 
			
		||||
        """Tests the capacity methods."""
 | 
			
		||||
        mock_vg.return_value = mock.Mock(
 | 
			
		||||
            capacity='5120', available_size='2048')
 | 
			
		||||
        self.assertEqual(5120.0, self.ld_adpt.capacity)
 | 
			
		||||
        self.assertEqual(3072.0, self.ld_adpt.capacity_used)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.tasks.storage.rm_vg_storage', autospec=True)
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._get_vg_wrap')
 | 
			
		||||
    def test_delete_disks(self, mock_vg, mock_rm_vg):
 | 
			
		||||
        self.ld_adpt.delete_disks('storage_elems')
 | 
			
		||||
        mock_vg.assert_called_once_with()
 | 
			
		||||
        mock_rm_vg.assert_called_once_with(
 | 
			
		||||
            mock_vg.return_value, vdisks='storage_elems')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.remove_maps', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func', autospec=True)
 | 
			
		||||
    def test_detach_disk(self, mock_match_fn, mock_rm_maps, mock_vios):
 | 
			
		||||
        mock_match_fn.return_value = 'match_func'
 | 
			
		||||
        mock_vios.return_value = self.vio_wrap
 | 
			
		||||
        mock_map1 = mock.Mock(backing_storage='back_stor1')
 | 
			
		||||
        mock_map2 = mock.Mock(backing_storage='back_stor2')
 | 
			
		||||
        mock_rm_maps.return_value = [mock_map1, mock_map2]
 | 
			
		||||
 | 
			
		||||
        back_stores = self.ld_adpt.detach_disk('instance')
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(['back_stor1', 'back_stor2'], back_stores)
 | 
			
		||||
        mock_match_fn.assert_called_once_with(pvm_stg.VDisk)
 | 
			
		||||
        mock_vios.assert_called_once_with(
 | 
			
		||||
            self.ld_adpt._adapter, uuid='vios-uuid',
 | 
			
		||||
            xag=[pvm_const.XAG.VIO_SMAP])
 | 
			
		||||
        mock_rm_maps.assert_called_with(self.vio_wrap, 'pvm_uuid',
 | 
			
		||||
                                        match_func=mock_match_fn.return_value)
 | 
			
		||||
        mock_vios.return_value.update.assert_called_once()
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.remove_vdisk_mapping',
 | 
			
		||||
                autospec=True)
 | 
			
		||||
    def test_disconnect_disk_from_mgmt(self, mock_rm_vdisk_map):
 | 
			
		||||
        self.ld_adpt.disconnect_disk_from_mgmt('vios-uuid', 'disk_name')
 | 
			
		||||
        mock_rm_vdisk_map.assert_called_with(
 | 
			
		||||
            self.ld_adpt._adapter, 'vios-uuid', 'mgmt_uuid',
 | 
			
		||||
            disk_names=['disk_name'])
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage._upload_image')
 | 
			
		||||
    def test_create_disk_from_image(self, mock_upload_image):
 | 
			
		||||
        mock_image_meta = mock.Mock()
 | 
			
		||||
        mock_image_meta.size = 30
 | 
			
		||||
        mock_upload_image.return_value = 'mock_img'
 | 
			
		||||
 | 
			
		||||
        self.ld_adpt.create_disk_from_image(
 | 
			
		||||
            'context', 'instance', mock_image_meta)
 | 
			
		||||
 | 
			
		||||
        mock_upload_image.assert_called_once_with(
 | 
			
		||||
            'context', 'instance', mock_image_meta)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.image.api.API.download')
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.driver.IterableToFileAdapter')
 | 
			
		||||
    @mock.patch('pypowervm.tasks.storage.upload_new_vdisk')
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_disk_name')
 | 
			
		||||
    def test_upload_image(self, mock_name, mock_upload, mock_iter, mock_dl):
 | 
			
		||||
        mock_meta = mock.Mock(id='1', size=1073741824, disk_format='raw')
 | 
			
		||||
        mock_upload.return_value = ['mock_img']
 | 
			
		||||
 | 
			
		||||
        mock_img = self.ld_adpt._upload_image('context', 'inst', mock_meta)
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('mock_img', mock_img)
 | 
			
		||||
        mock_name.assert_called_once_with(
 | 
			
		||||
            disk_dvr.DiskType.BOOT, 'inst', short=True)
 | 
			
		||||
        mock_dl.assert_called_once_with('context', '1')
 | 
			
		||||
        mock_iter.assert_called_once_with(mock_dl.return_value)
 | 
			
		||||
        mock_upload.assert_called_once_with(
 | 
			
		||||
            self.adpt, 'vios-uuid', self.vg_uuid, mock_iter.return_value,
 | 
			
		||||
            mock_name.return_value, 1073741824, d_size=1073741824,
 | 
			
		||||
            upload_type=tsk_stg.UploadType.IO_STREAM, file_format='raw')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.add_map', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.build_vscsi_mapping',
 | 
			
		||||
                autospec=True)
 | 
			
		||||
    def test_attach_disk(self, mock_bldmap, mock_addmap):
 | 
			
		||||
        def test_afs(add_func):
 | 
			
		||||
            # Verify the internal add_func
 | 
			
		||||
            self.assertEqual(mock_addmap.return_value, add_func(self.vio_wrap))
 | 
			
		||||
            mock_bldmap.assert_called_once_with(
 | 
			
		||||
                self.ld_adpt._host_uuid, self.vio_wrap, 'pvm_uuid',
 | 
			
		||||
                'disk_info')
 | 
			
		||||
            mock_addmap.assert_called_once_with(
 | 
			
		||||
                self.vio_wrap, mock_bldmap.return_value)
 | 
			
		||||
 | 
			
		||||
        self.mock_wtsk.add_functor_subtask.side_effect = test_afs
 | 
			
		||||
        self.ld_adpt.attach_disk('instance', 'disk_info', self.mock_ftsk)
 | 
			
		||||
        self.pvm_uuid.assert_called_once_with('instance')
 | 
			
		||||
        self.assertEqual(1, self.mock_wtsk.add_functor_subtask.call_count)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.storage.VG.get')
 | 
			
		||||
    def test_get_vg_wrap(self, mock_vg):
 | 
			
		||||
        vg_wrap = self.ld_adpt._get_vg_wrap()
 | 
			
		||||
        self.assertEqual(mock_vg.return_value, vg_wrap)
 | 
			
		||||
        mock_vg.assert_called_once_with(
 | 
			
		||||
            self.adpt, uuid=self.vg_uuid, parent_type=pvm_vios.VIOS,
 | 
			
		||||
            parent_uuid='vios-uuid')
 | 
			
		||||
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.find_maps', autospec=True)
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.localdisk.LocalStorage.'
 | 
			
		||||
                '_disk_match_func')
 | 
			
		||||
    def test_get_bootdisk_path(self, mock_match_fn, mock_findmaps,
 | 
			
		||||
                                         mock_vios):
 | 
			
		||||
        mock_vios.return_value = self.vio_wrap
 | 
			
		||||
 | 
			
		||||
        # No maps found
 | 
			
		||||
        mock_findmaps.return_value = None
 | 
			
		||||
        devname = self.ld_adpt.get_bootdisk_path('inst', 'vios_uuid')
 | 
			
		||||
        self.pvm_uuid.assert_called_once_with('inst')
 | 
			
		||||
        mock_match_fn.assert_called_once_with(disk_dvr.DiskType.BOOT, 'inst')
 | 
			
		||||
        mock_vios.assert_called_once_with(
 | 
			
		||||
            self.adpt, uuid='vios_uuid', xag=[pvm_const.XAG.VIO_SMAP])
 | 
			
		||||
        mock_findmaps.assert_called_once_with(
 | 
			
		||||
            self.vio_wrap.scsi_mappings,
 | 
			
		||||
            client_lpar_id='pvm_uuid',
 | 
			
		||||
            match_func=mock_match_fn.return_value)
 | 
			
		||||
        self.assertIsNone(devname)
 | 
			
		||||
 | 
			
		||||
        # Good map
 | 
			
		||||
        mock_lu = mock.Mock()
 | 
			
		||||
        mock_lu.server_adapter.backing_dev_name = 'devname'
 | 
			
		||||
        mock_findmaps.return_value = [mock_lu]
 | 
			
		||||
        devname = self.ld_adpt.get_bootdisk_path('inst', 'vios_uuid')
 | 
			
		||||
        self.assertEqual('devname', devname)
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.find_maps')
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.virtual_io_server.VIOS.get')
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.storage.VG.get', new=mock.Mock())
 | 
			
		||||
    def test_get_bootdisk_iter(self, mock_vios, mock_find_maps, mock_lw):
 | 
			
		||||
        inst, lpar_wrap, vios1 = self._bld_mocks_for_instance_disk()
 | 
			
		||||
        mock_lw.return_value = lpar_wrap
 | 
			
		||||
 | 
			
		||||
        # Good path
 | 
			
		||||
        mock_vios.return_value = vios1
 | 
			
		||||
        for vdisk, vios in self.ld_adpt._get_bootdisk_iter(inst):
 | 
			
		||||
            self.assertEqual(vios1.scsi_mappings[0].backing_storage, vdisk)
 | 
			
		||||
            self.assertEqual(vios1.uuid, vios.uuid)
 | 
			
		||||
        mock_vios.assert_called_once_with(
 | 
			
		||||
            self.adpt, uuid='vios-uuid', xag=[pvm_const.XAG.VIO_SMAP])
 | 
			
		||||
 | 
			
		||||
        # Not found, no storage of that name.
 | 
			
		||||
        mock_vios.reset_mock()
 | 
			
		||||
        mock_find_maps.return_value = []
 | 
			
		||||
        for vdisk, vios in self.ld_adpt._get_bootdisk_iter(inst):
 | 
			
		||||
            self.fail('Should not have found any storage elements.')
 | 
			
		||||
        mock_vios.assert_called_once_with(
 | 
			
		||||
            self.adpt, uuid='vios-uuid', xag=[pvm_const.XAG.VIO_SMAP])
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.driver.DiskAdapter._get_bootdisk_iter',
 | 
			
		||||
                autospec=True)
 | 
			
		||||
    @mock.patch('nova.virt.powervm.vm.get_instance_wrapper', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.tasks.scsi_mapper.add_vscsi_mapping', autospec=True)
 | 
			
		||||
    def test_connect_instance_disk_to_mgmt(self, mock_add, mock_lw, mock_iter):
 | 
			
		||||
        inst, lpar_wrap, vios1 = self._bld_mocks_for_instance_disk()
 | 
			
		||||
        mock_lw.return_value = lpar_wrap
 | 
			
		||||
 | 
			
		||||
        # Good path
 | 
			
		||||
        mock_iter.return_value = [(vios1.scsi_mappings[0].backing_storage,
 | 
			
		||||
                                   vios1)]
 | 
			
		||||
        vdisk, vios = self.ld_adpt.connect_instance_disk_to_mgmt(inst)
 | 
			
		||||
        self.assertEqual(vios1.scsi_mappings[0].backing_storage, vdisk)
 | 
			
		||||
        self.assertIs(vios1, vios)
 | 
			
		||||
        self.assertEqual(1, mock_add.call_count)
 | 
			
		||||
        mock_add.assert_called_with('host_uuid', vios, 'mgmt_uuid', vdisk)
 | 
			
		||||
 | 
			
		||||
        # add_vscsi_mapping raises.  Show-stopper since only one VIOS.
 | 
			
		||||
        mock_add.reset_mock()
 | 
			
		||||
        mock_add.side_effect = Exception
 | 
			
		||||
        self.assertRaises(exception.InstanceDiskMappingFailed,
 | 
			
		||||
                          self.ld_adpt.connect_instance_disk_to_mgmt, inst)
 | 
			
		||||
        self.assertEqual(1, mock_add.call_count)
 | 
			
		||||
 | 
			
		||||
        # Not found
 | 
			
		||||
        mock_add.reset_mock()
 | 
			
		||||
        mock_iter.return_value = []
 | 
			
		||||
        self.assertRaises(exception.InstanceDiskMappingFailed,
 | 
			
		||||
                          self.ld_adpt.connect_instance_disk_to_mgmt, inst)
 | 
			
		||||
        self.assertFalse(mock_add.called)
 | 
			
		||||
 | 
			
		||||
    def _bld_mocks_for_instance_disk(self):
 | 
			
		||||
        inst = mock.Mock()
 | 
			
		||||
        inst.name = 'Name Of Instance'
 | 
			
		||||
        inst.uuid = uuids.inst_uuid
 | 
			
		||||
        lpar_wrap = mock.Mock()
 | 
			
		||||
        lpar_wrap.id = 2
 | 
			
		||||
        vios1 = self.vio_wrap
 | 
			
		||||
        back_stor_name = 'b_Name_Of__' + inst.uuid[:4]
 | 
			
		||||
        vios1.scsi_mappings[0].backing_storage.name = back_stor_name
 | 
			
		||||
        return inst, lpar_wrap, vios1
 | 
			
		||||
@@ -73,10 +73,10 @@ class TestPowerVMDriver(test.NoDBTestCase):
 | 
			
		||||
 | 
			
		||||
    @mock.patch('nova.image.API')
 | 
			
		||||
    @mock.patch('pypowervm.tasks.storage.ComprehensiveScrub', autospec=True)
 | 
			
		||||
    @mock.patch('nova.virt.powervm.disk.ssp.SSPDiskAdapter')
 | 
			
		||||
    @mock.patch('oslo_utils.importutils.import_object_ns', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.wrappers.managed_system.System', autospec=True)
 | 
			
		||||
    @mock.patch('pypowervm.tasks.partition.validate_vios_ready', autospec=True)
 | 
			
		||||
    def test_init_host(self, mock_vvr, mock_sys, mock_ssp, mock_scrub,
 | 
			
		||||
    def test_init_host(self, mock_vvr, mock_sys, mock_import, mock_scrub,
 | 
			
		||||
                       mock_img):
 | 
			
		||||
        mock_hostw = mock.Mock(uuid='uuid')
 | 
			
		||||
        mock_sys.get.return_value = [mock_hostw]
 | 
			
		||||
@@ -90,8 +90,10 @@ class TestPowerVMDriver(test.NoDBTestCase):
 | 
			
		||||
        self.assertEqual(mock_hostw, self.drv.host_wrapper)
 | 
			
		||||
        mock_scrub.assert_called_once_with(self.drv.adapter)
 | 
			
		||||
        mock_scrub.return_value.execute.assert_called_once_with()
 | 
			
		||||
        mock_ssp.assert_called_once_with(self.drv.adapter, 'uuid')
 | 
			
		||||
        self.assertEqual(mock_ssp.return_value, self.drv.disk_dvr)
 | 
			
		||||
        mock_import.assert_called_once_with(
 | 
			
		||||
            'nova.virt.powervm.disk', 'localdisk.LocalStorage',
 | 
			
		||||
            self.drv.adapter, 'uuid')
 | 
			
		||||
        self.assertEqual(mock_import.return_value, self.drv.disk_dvr)
 | 
			
		||||
        mock_img.assert_called_once_with()
 | 
			
		||||
        self.assertEqual(mock_img.return_value, self.drv.image_api)
 | 
			
		||||
 | 
			
		||||
@@ -309,6 +311,10 @@ class TestPowerVMDriver(test.NoDBTestCase):
 | 
			
		||||
        mock_rm.assert_called_once_with(
 | 
			
		||||
            stg_elem='stg_elem', vios_wrap='vios_wrap', disk_path='disk_path')
 | 
			
		||||
 | 
			
		||||
        self.drv.disk_dvr.capabilities = {'snapshot': False}
 | 
			
		||||
        self.assertRaises(exception.NotSupportedWithOption, self.drv.snapshot,
 | 
			
		||||
                         'context', self.inst, 'image_id', 'update_task_state')
 | 
			
		||||
 | 
			
		||||
    def test_power_on(self):
 | 
			
		||||
        self.drv.power_on('context', self.inst, 'network_info')
 | 
			
		||||
        self.pwron.assert_called_once_with(self.adp, self.inst)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										211
									
								
								nova/virt/powervm/disk/localdisk.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								nova/virt/powervm/disk/localdisk.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# Copyright 2015, 2018 IBM Corp.
 | 
			
		||||
#
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
 | 
			
		||||
#    not use this file except in compliance with the License. You may obtain
 | 
			
		||||
#    a copy of the License at
 | 
			
		||||
#
 | 
			
		||||
#         http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
#
 | 
			
		||||
#    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
			
		||||
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
import oslo_log.log as logging
 | 
			
		||||
from pypowervm import const as pvm_const
 | 
			
		||||
from pypowervm.tasks import scsi_mapper as tsk_map
 | 
			
		||||
from pypowervm.tasks import storage as tsk_stg
 | 
			
		||||
from pypowervm.wrappers import storage as pvm_stg
 | 
			
		||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
 | 
			
		||||
 | 
			
		||||
from nova import conf
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova import image
 | 
			
		||||
from nova.virt.powervm.disk import driver as disk_dvr
 | 
			
		||||
from nova.virt.powervm import vm
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = conf.CONF
 | 
			
		||||
IMAGE_API = image.API()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LocalStorage(disk_dvr.DiskAdapter):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, adapter, host_uuid):
 | 
			
		||||
        super(LocalStorage, self).__init__(adapter, host_uuid)
 | 
			
		||||
 | 
			
		||||
        self.capabilities = {
 | 
			
		||||
            'shared_storage': False,
 | 
			
		||||
            'has_imagecache': False,
 | 
			
		||||
            # NOTE(efried): 'snapshot' capability set dynamically below.
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # Query to get the Volume Group UUID
 | 
			
		||||
        if not CONF.powervm.volume_group_name:
 | 
			
		||||
            raise exception.OptRequiredIfOtherOptValue(
 | 
			
		||||
                if_opt='disk_driver', if_value='localdisk',
 | 
			
		||||
                then_opt='volume_group_name')
 | 
			
		||||
        self.vg_name = CONF.powervm.volume_group_name
 | 
			
		||||
        vios_w, vg_w = tsk_stg.find_vg(adapter, self.vg_name)
 | 
			
		||||
        self._vios_uuid = vios_w.uuid
 | 
			
		||||
        self.vg_uuid = vg_w.uuid
 | 
			
		||||
        # Set the 'snapshot' capability dynamically. If we're hosting I/O on
 | 
			
		||||
        # the management partition, we can snapshot. If we're hosting I/O on
 | 
			
		||||
        # traditional VIOS, we are limited by the fact that a VSCSI device
 | 
			
		||||
        # can't be mapped to two partitions (the VIOS and the management) at
 | 
			
		||||
        # once.
 | 
			
		||||
        self.capabilities['snapshot'] = self.mp_uuid == self._vios_uuid
 | 
			
		||||
        LOG.info("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]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _disk_match_func(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.
 | 
			
		||||
        """
 | 
			
		||||
        disk_name = LocalStorage._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."""
 | 
			
		||||
        vg_wrap = self._get_vg_wrap()
 | 
			
		||||
        return float(vg_wrap.capacity)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def capacity_used(self):
 | 
			
		||||
        """Capacity of the storage in gigabytes that is used."""
 | 
			
		||||
        vg_wrap = self._get_vg_wrap()
 | 
			
		||||
        # Subtract available from capacity
 | 
			
		||||
        return float(vg_wrap.capacity) - float(vg_wrap.available_size)
 | 
			
		||||
 | 
			
		||||
    def delete_disks(self, storage_elems):
 | 
			
		||||
        """Removes the specified disks.
 | 
			
		||||
 | 
			
		||||
        :param storage_elems: A list of the storage elements that are to be
 | 
			
		||||
                              deleted. Derived from the return value from
 | 
			
		||||
                              detach_disk.
 | 
			
		||||
        """
 | 
			
		||||
        # All of localdisk is done against the volume group. So reload
 | 
			
		||||
        # that (to get new etag) and then update against it.
 | 
			
		||||
        tsk_stg.rm_vg_storage(self._get_vg_wrap(), vdisks=storage_elems)
 | 
			
		||||
 | 
			
		||||
    def detach_disk(self, instance):
 | 
			
		||||
        """Detaches the storage adapters from the image disk.
 | 
			
		||||
 | 
			
		||||
        :param instance: Instance to disconnect the image for.
 | 
			
		||||
        :return: A list of all the backing storage elements that were
 | 
			
		||||
                 disconnected from the I/O Server and VM.
 | 
			
		||||
        """
 | 
			
		||||
        lpar_uuid = vm.get_pvm_uuid(instance)
 | 
			
		||||
 | 
			
		||||
        # Build the match function
 | 
			
		||||
        match_func = tsk_map.gen_match_func(pvm_stg.VDisk)
 | 
			
		||||
 | 
			
		||||
        vios_w = pvm_vios.VIOS.get(
 | 
			
		||||
            self._adapter, uuid=self._vios_uuid, xag=[pvm_const.XAG.VIO_SMAP])
 | 
			
		||||
 | 
			
		||||
        # Remove the mappings.
 | 
			
		||||
        mappings = tsk_map.remove_maps(
 | 
			
		||||
            vios_w, lpar_uuid, match_func=match_func)
 | 
			
		||||
 | 
			
		||||
        # Update the VIOS with the removed mappings.
 | 
			
		||||
        vios_w.update()
 | 
			
		||||
 | 
			
		||||
        return [x.backing_storage for x in mappings]
 | 
			
		||||
 | 
			
		||||
    def disconnect_disk_from_mgmt(self, vios_uuid, disk_name):
 | 
			
		||||
        """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.
 | 
			
		||||
        """
 | 
			
		||||
        tsk_map.remove_vdisk_mapping(self._adapter, vios_uuid, self.mp_uuid,
 | 
			
		||||
                                     disk_names=[disk_name])
 | 
			
		||||
        LOG.info("Unmapped boot disk %(disk_name)s from the management "
 | 
			
		||||
                 "partition from Virtual I/O Server %(vios_name)s.",
 | 
			
		||||
                 {'disk_name': disk_name, 'mp_uuid': self.mp_uuid,
 | 
			
		||||
                 'vios_name': vios_uuid})
 | 
			
		||||
 | 
			
		||||
    def create_disk_from_image(self, context, instance, image_meta):
 | 
			
		||||
        """Creates a disk and copies the specified image to it.
 | 
			
		||||
 | 
			
		||||
        Cleans up the created disk if an error occurs.
 | 
			
		||||
 | 
			
		||||
        :param context: nova context used to retrieve image from glance
 | 
			
		||||
        :param instance: instance to create the disk for.
 | 
			
		||||
        :param image_meta: The metadata of the image of the instance.
 | 
			
		||||
        :return: The backing pypowervm storage object that was created.
 | 
			
		||||
        """
 | 
			
		||||
        LOG.info('Create disk.', instance=instance)
 | 
			
		||||
 | 
			
		||||
        return self._upload_image(context, instance, image_meta)
 | 
			
		||||
 | 
			
		||||
        # TODO(esberglu): Copy vdisk when implementing image cache.
 | 
			
		||||
 | 
			
		||||
    def _upload_image(self, context, instance, image_meta):
 | 
			
		||||
        """Upload a new image.
 | 
			
		||||
 | 
			
		||||
        :param context: Nova context used to retrieve image from glance.
 | 
			
		||||
        :param image_meta: The metadata of the image of the instance.
 | 
			
		||||
        :return: The virtual disk containing the image.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        img_name = self._get_disk_name(disk_dvr.DiskType.BOOT, instance,
 | 
			
		||||
                                       short=True)
 | 
			
		||||
 | 
			
		||||
        # TODO(esberglu) Add check for cached image when adding imagecache.
 | 
			
		||||
 | 
			
		||||
        return tsk_stg.upload_new_vdisk(
 | 
			
		||||
            self._adapter, self._vios_uuid, self.vg_uuid,
 | 
			
		||||
            disk_dvr.IterableToFileAdapter(
 | 
			
		||||
                IMAGE_API.download(context, image_meta.id)), img_name,
 | 
			
		||||
            image_meta.size, d_size=image_meta.size,
 | 
			
		||||
            upload_type=tsk_stg.UploadType.IO_STREAM,
 | 
			
		||||
            file_format=image_meta.disk_format)[0]
 | 
			
		||||
 | 
			
		||||
    def attach_disk(self, instance, disk_info, stg_ftsk):
 | 
			
		||||
        """Attaches the disk image to the Virtual Machine.
 | 
			
		||||
 | 
			
		||||
        :param instance: nova instance to connect the disk to.
 | 
			
		||||
        :param disk_info: The pypowervm storage element returned from
 | 
			
		||||
                          create_disk_from_image.  Ex. VOptMedia, VDisk, LU,
 | 
			
		||||
                          or PV.
 | 
			
		||||
        :param stg_ftsk: The pypowervm transaction FeedTask for the
 | 
			
		||||
                         I/O Operations. The Virtual I/O Server mapping updates
 | 
			
		||||
                         will be added to the FeedTask. This defers the updates
 | 
			
		||||
                         to some later point in time.
 | 
			
		||||
        """
 | 
			
		||||
        lpar_uuid = vm.get_pvm_uuid(instance)
 | 
			
		||||
 | 
			
		||||
        def add_func(vios_w):
 | 
			
		||||
            LOG.info("Adding logical volume disk connection to VIOS %(vios)s.",
 | 
			
		||||
                     {'vios': vios_w.name}, instance=instance)
 | 
			
		||||
            mapping = tsk_map.build_vscsi_mapping(
 | 
			
		||||
                self._host_uuid, vios_w, lpar_uuid, disk_info)
 | 
			
		||||
            return tsk_map.add_map(vios_w, mapping)
 | 
			
		||||
 | 
			
		||||
        stg_ftsk.wrapper_tasks[self._vios_uuid].add_functor_subtask(add_func)
 | 
			
		||||
 | 
			
		||||
    def _get_vg_wrap(self):
 | 
			
		||||
        return pvm_stg.VG.get(self._adapter, uuid=self.vg_uuid,
 | 
			
		||||
                              parent_type=pvm_vios.VIOS,
 | 
			
		||||
                              parent_uuid=self._vios_uuid)
 | 
			
		||||
@@ -15,6 +15,7 @@
 | 
			
		||||
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
from oslo_utils import excutils
 | 
			
		||||
from oslo_utils import importutils
 | 
			
		||||
from pypowervm import adapter as pvm_apt
 | 
			
		||||
from pypowervm import const as pvm_const
 | 
			
		||||
from pypowervm import exceptions as pvm_exc
 | 
			
		||||
@@ -35,7 +36,6 @@ from nova.i18n import _
 | 
			
		||||
from nova import image
 | 
			
		||||
from nova.virt import configdrive
 | 
			
		||||
from nova.virt import driver
 | 
			
		||||
from nova.virt.powervm.disk import ssp
 | 
			
		||||
from nova.virt.powervm import host as pvm_host
 | 
			
		||||
from nova.virt.powervm.tasks import base as tf_base
 | 
			
		||||
from nova.virt.powervm.tasks import image as tf_img
 | 
			
		||||
@@ -47,6 +47,12 @@ from nova.virt.powervm import vm
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
 | 
			
		||||
DISK_ADPT_NS = 'nova.virt.powervm.disk'
 | 
			
		||||
DISK_ADPT_MAPPINGS = {
 | 
			
		||||
    'localdisk': 'localdisk.LocalStorage',
 | 
			
		||||
    'ssp': 'ssp.SSPDiskAdapter'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PowerVMDriver(driver.ComputeDriver):
 | 
			
		||||
    """PowerVM NovaLink Implementation of Compute Driver.
 | 
			
		||||
@@ -91,9 +97,9 @@ class PowerVMDriver(driver.ComputeDriver):
 | 
			
		||||
        pvm_stor.ComprehensiveScrub(self.adapter).execute()
 | 
			
		||||
 | 
			
		||||
        # Initialize the disk adapter
 | 
			
		||||
        # TODO(efried): Other disk adapters (localdisk), by conf selection.
 | 
			
		||||
        self.disk_dvr = ssp.SSPDiskAdapter(self.adapter,
 | 
			
		||||
                                           self.host_wrapper.uuid)
 | 
			
		||||
        self.disk_dvr = importutils.import_object_ns(
 | 
			
		||||
            DISK_ADPT_NS, DISK_ADPT_MAPPINGS[CONF.powervm.disk_driver.lower()],
 | 
			
		||||
            self.adapter, self.host_wrapper.uuid)
 | 
			
		||||
        self.image_api = image.API()
 | 
			
		||||
 | 
			
		||||
        LOG.info("The PowerVM compute driver has been initialized.")
 | 
			
		||||
@@ -312,8 +318,13 @@ class PowerVMDriver(driver.ComputeDriver):
 | 
			
		||||
            nova.compute.task_states.IMAGE_SNAPSHOT. See
 | 
			
		||||
            nova.objects.instance.Instance.save for expected_task_state usage.
 | 
			
		||||
        """
 | 
			
		||||
        # TODO(esberglu) Add check for disk driver snapshot capability when
 | 
			
		||||
        # additional disk drivers are implemented.
 | 
			
		||||
 | 
			
		||||
        if not self.disk_dvr.capabilities.get('snapshot'):
 | 
			
		||||
            raise exc.NotSupportedWithOption(
 | 
			
		||||
                message=_("The snapshot operation is not supported in "
 | 
			
		||||
                          "conjunction with a [powervm]/disk_driver setting "
 | 
			
		||||
                          "of %s.") % CONF.powervm.disk_driver)
 | 
			
		||||
 | 
			
		||||
        self._log_operation('snapshot', instance)
 | 
			
		||||
 | 
			
		||||
        # Define the flow.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								releasenotes/notes/powervm-localdisk-ccdf2347226303a8.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								releasenotes/notes/powervm-localdisk-ccdf2347226303a8.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
---
 | 
			
		||||
features:
 | 
			
		||||
  - |
 | 
			
		||||
    The PowerVM virt driver now supports booting from local ephemeral disk.
 | 
			
		||||
    Two new configuration options have been introduced to the ``powervm``
 | 
			
		||||
    configuration group, ``disk_driver`` and ``volume_group_name``. The former
 | 
			
		||||
    allows the selection of either ssp or localdisk for the PowerVM disk
 | 
			
		||||
    driver. The latter specifies the name of the volume group when using the
 | 
			
		||||
    localdisk disk driver.
 | 
			
		||||
upgrade:
 | 
			
		||||
  - |
 | 
			
		||||
    The PowerVM virt driver previously used the PowerVM Shared Storage Pool
 | 
			
		||||
    disk driver by default. The default disk driver for PowerVM is now
 | 
			
		||||
    localdisk. See configuration option ``[powervm]/disk_driver`` for usage
 | 
			
		||||
    details.
 | 
			
		||||
		Reference in New Issue
	
	Block a user