Experimental: File I/O Volume Driver Implementation
Implements the volume driver that supports file backed cinder volumes. Resolves long standing issue with how our volume adapters are created and now allows for a single host to support multiple different volume types at once. This is considered experimental. Full support should land in Pike. To use, the NovaLink nightly builds must be utilized. Change-Id: Ia78d2ed17232cc0f42fed1391786384e4dea28c1 Partially-Implements: bp/file-io-cinder-connector
This commit is contained in:
parent
8651e2213c
commit
ebee3eae88
@ -1,4 +1,4 @@
|
||||
# Copyright 2016 IBM Corp.
|
||||
# Copyright 2016, 2017 IBM Corp.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -24,7 +24,6 @@ import nova_powervm.virt.powervm.driver as real_drv
|
||||
|
||||
LOG = real_drv.LOG
|
||||
CONF = real_drv.CONF
|
||||
VOLUME_DRIVER_MAPPINGS = real_drv.VOLUME_DRIVER_MAPPINGS
|
||||
DISK_ADPT_NS = real_drv.DISK_ADPT_NS
|
||||
DISK_ADPT_MAPPINGS = real_drv.DISK_ADPT_MAPPINGS
|
||||
NVRAM_NS = real_drv.NVRAM_NS
|
||||
|
@ -116,7 +116,7 @@ ssp_opts = [
|
||||
vol_adapter_opts = [
|
||||
cfg.StrOpt('fc_attach_strategy',
|
||||
choices=['vscsi', 'npiv'], ignore_case=True,
|
||||
default='vscsi',
|
||||
default='vscsi', mutable=True,
|
||||
help='The Fibre Channel Volume Strategy defines how FC Cinder '
|
||||
'volumes should be attached to the Virtual Machine. The '
|
||||
'options are: npiv or vscsi. If npiv is selected then '
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2014, 2016 IBM Corp.
|
||||
# Copyright 2014, 2017 IBM Corp.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -13,7 +13,6 @@
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
@ -51,7 +50,6 @@ from nova_powervm.tests.virt.powervm import fixtures as fx
|
||||
from nova_powervm.virt.powervm import exception as p_exc
|
||||
from nova_powervm.virt.powervm import live_migration as lpm
|
||||
from nova_powervm.virt.powervm import vm
|
||||
from nova_powervm.virt.powervm import volume as vol_attach
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
logging.basicConfig()
|
||||
@ -162,15 +160,6 @@ class TestPowerVMDriver(test.TestCase):
|
||||
self.lpm_inst.uuid = 'inst1'
|
||||
self.drv.live_migrations = {'inst1': self.lpm}
|
||||
|
||||
def _vol_drv_maps(self):
|
||||
VOLUME_DRIVER_MAPPINGS = {
|
||||
'fibre_channel': vol_attach.FC_STRATEGY_MAPPING[
|
||||
driver.CONF.powervm.fc_attach_strategy.lower()],
|
||||
'iscsi': vol_attach.NETWORK_STRATEGY_MAPPING[
|
||||
driver.CONF.powervm.network_attach_strategy.lower()],
|
||||
}
|
||||
return VOLUME_DRIVER_MAPPINGS
|
||||
|
||||
def test_driver_create(self):
|
||||
"""Validates that a driver of the PowerVM type can be initialized."""
|
||||
test_drv = driver.PowerVMDriver(fake.FakeVirtAPI())
|
||||
@ -863,44 +852,40 @@ class TestPowerVMDriver(test.TestCase):
|
||||
ret = self.drv._is_booted_from_volume(None)
|
||||
self.assertFalse(ret)
|
||||
|
||||
@mock.patch('nova_powervm.virt.powervm.driver.VOLUME_DRIVER_MAPPINGS')
|
||||
def test_get_inst_xag(self, mock_mapping):
|
||||
def getitem(name):
|
||||
return self._vol_drv_maps()[name]
|
||||
mock_mapping.__getitem__.side_effect = getitem
|
||||
|
||||
self.flags(volume_adapter='fibre_channel', group='powervm')
|
||||
def test_get_inst_xag(self):
|
||||
# No volumes - should be just the SCSI mapping
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), None)
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], xag)
|
||||
|
||||
# The vSCSI Volume attach - only needs the SCSI mapping.
|
||||
self.flags(fc_attach_strategy='vscsi', group='powervm')
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock.Mock()])
|
||||
mock_bdm = {'connection_info':
|
||||
{'driver_volume_type': 'fibre_channel'}}
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock_bdm])
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], xag)
|
||||
|
||||
# The NPIV volume attach - requires SCSI, Storage and FC Mapping
|
||||
self.flags(fc_attach_strategy='npiv', group='powervm')
|
||||
mock_mapping.return_value = self._vol_drv_maps()
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock.Mock()])
|
||||
self.assertEqual({pvm_const.XAG.VIO_STOR,
|
||||
pvm_const.XAG.VIO_SMAP,
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock_bdm])
|
||||
self.assertEqual({pvm_const.XAG.VIO_STOR, pvm_const.XAG.VIO_SMAP,
|
||||
pvm_const.XAG.VIO_FMAP}, set(xag))
|
||||
|
||||
# The vSCSI Volume attach - Ensure case insensitive.
|
||||
self.flags(fc_attach_strategy='VSCSI', group='powervm')
|
||||
mock_mapping.return_value = self._vol_drv_maps()
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock.Mock()])
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock_bdm])
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], xag)
|
||||
|
||||
# The iSCSI volume attach - only nees the SCSI mapping.
|
||||
self.flags(volume_adapter='iscsi', group='powervm')
|
||||
mock_mapping.return_value = self._vol_drv_maps()
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock.Mock()])
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], xag)
|
||||
# Validate the other volume types only return SCSI mappings
|
||||
vol_types = ['iscsi', 'gpfs', 'local', 'nfs']
|
||||
for vol_type in vol_types:
|
||||
self.flags(volume_adapter='iscsi', group='powervm')
|
||||
mock_bdm = {'connection_info':
|
||||
{'driver_volume_type': vol_type}}
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock_bdm])
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], xag)
|
||||
|
||||
# If a recreate, all should be returned
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock.Mock()], recreate=True)
|
||||
xag = self.drv._get_inst_xag(mock.Mock(), [mock_bdm], recreate=True)
|
||||
self.assertEqual({pvm_const.XAG.VIO_STOR,
|
||||
pvm_const.XAG.VIO_SMAP,
|
||||
pvm_const.XAG.VIO_FMAP}, set(xag))
|
||||
@ -2041,8 +2026,8 @@ class TestPowerVMDriver(test.TestCase):
|
||||
|
||||
def _get_results(block_device_info=None, bdms=None):
|
||||
# Patch so we get the same mock back each time.
|
||||
with mock.patch.object(self.drv, '_get_inst_vol_adpt',
|
||||
return_value=vol_adpt):
|
||||
with mock.patch('nova_powervm.virt.powervm.volume.'
|
||||
'build_volume_driver', return_value=vol_adpt):
|
||||
return [
|
||||
(bdm, vol_drv) for bdm, vol_drv in self.drv._vol_drv_iter(
|
||||
'context', self.inst,
|
||||
|
@ -16,10 +16,17 @@
|
||||
|
||||
import mock
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
import six
|
||||
|
||||
from nova import test
|
||||
|
||||
from nova_powervm.virt.powervm import volume
|
||||
from nova_powervm.virt.powervm.volume import gpfs
|
||||
from nova_powervm.virt.powervm.volume import iscsi
|
||||
from nova_powervm.virt.powervm.volume import local
|
||||
from nova_powervm.virt.powervm.volume import nfs
|
||||
from nova_powervm.virt.powervm.volume import npiv
|
||||
from nova_powervm.virt.powervm.volume import vscsi
|
||||
|
||||
|
||||
class TestVolumeAdapter(test.TestCase):
|
||||
@ -39,6 +46,14 @@ class TestVolumeAdapter(test.TestCase):
|
||||
|
||||
class TestInitMethods(test.TestCase):
|
||||
|
||||
# Volume driver types to classes
|
||||
volume_drivers = {
|
||||
'iscsi': iscsi.IscsiVolumeAdapter,
|
||||
'local': local.LocalVolumeAdapter,
|
||||
'nfs': nfs.NFSVolumeAdapter,
|
||||
'gpfs': gpfs.GPFSVolumeAdapter,
|
||||
}
|
||||
|
||||
@mock.patch('pypowervm.tasks.hdisk.discover_iscsi_initiator')
|
||||
@mock.patch('pypowervm.tasks.partition.get_mgmt_partition')
|
||||
def test_get_iscsi_initiator(self, mock_mgmt, mock_iscsi_init):
|
||||
@ -61,3 +76,58 @@ class TestInitMethods(test.TestCase):
|
||||
self.assertEqual('test_initiator',
|
||||
volume.get_iscsi_initiator(mock_adpt))
|
||||
self.assertEqual(1, mock_mgmt.call_count)
|
||||
|
||||
def test_get_volume_class(self):
|
||||
for vol_type, class_type in six.iteritems(self.volume_drivers):
|
||||
self.assertEqual(class_type, volume.get_volume_class(vol_type))
|
||||
|
||||
# Try the fibre as vscsi
|
||||
self.flags(fc_attach_strategy='vscsi', group='powervm')
|
||||
self.assertEqual(vscsi.PVVscsiFCVolumeAdapter,
|
||||
volume.get_volume_class('fibre_channel'))
|
||||
|
||||
# Try the fibre as npiv
|
||||
self.flags(fc_attach_strategy='npiv', group='powervm')
|
||||
self.assertEqual(npiv.NPIVVolumeAdapter,
|
||||
volume.get_volume_class('fibre_channel'))
|
||||
|
||||
def test_build_volume_driver(self):
|
||||
for vol_type, class_type in six.iteritems(self.volume_drivers):
|
||||
vdrv = volume.build_volume_driver(
|
||||
mock.Mock(), "abc123", mock.Mock(uuid='abc1'),
|
||||
{'driver_volume_type': vol_type})
|
||||
self.assertIsInstance(vdrv, class_type)
|
||||
|
||||
# Try the fibre as vscsi
|
||||
self.flags(fc_attach_strategy='vscsi', group='powervm')
|
||||
vdrv = volume.build_volume_driver(
|
||||
mock.Mock(), "abc123", mock.Mock(uuid='abc1'),
|
||||
{'driver_volume_type': 'fibre_channel'})
|
||||
self.assertIsInstance(vdrv, vscsi.PVVscsiFCVolumeAdapter)
|
||||
|
||||
# Try the fibre as npiv
|
||||
self.flags(fc_attach_strategy='npiv', group='powervm')
|
||||
vdrv = volume.build_volume_driver(
|
||||
mock.Mock(), "abc123", mock.Mock(uuid='abc1'),
|
||||
{'driver_volume_type': 'fibre_channel'})
|
||||
self.assertIsInstance(vdrv, npiv.NPIVVolumeAdapter)
|
||||
|
||||
def test_hostname_for_volume(self):
|
||||
self.flags(host='test_host')
|
||||
mock_instance = mock.Mock()
|
||||
mock_instance.name = 'instance'
|
||||
|
||||
# Try the fibre as vscsi
|
||||
self.flags(fc_attach_strategy='vscsi', group='powervm')
|
||||
self.assertEqual("test_host",
|
||||
volume.get_hostname_for_volume(mock_instance))
|
||||
|
||||
# Try the fibre as npiv
|
||||
self.flags(fc_attach_strategy='npiv', group='powervm')
|
||||
self.assertEqual("test_host_instance",
|
||||
volume.get_hostname_for_volume(mock_instance))
|
||||
|
||||
# NPIV with long host name
|
||||
self.flags(host='really_long_host_name_too_long')
|
||||
self.assertEqual("really_long_host_nam_instance",
|
||||
volume.get_hostname_for_volume(mock_instance))
|
||||
|
99
nova_powervm/tests/virt/powervm/volume/test_fileio.py
Normal file
99
nova_powervm/tests/virt/powervm/volume/test_fileio.py
Normal file
@ -0,0 +1,99 @@
|
||||
# Copyright 2017 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 mock
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tests import test_fixtures as pvm_fx
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
|
||||
from nova_powervm.tests.virt.powervm.volume import test_driver as test_vol
|
||||
from nova_powervm.virt.powervm.volume import fileio as v_drv
|
||||
|
||||
|
||||
class FakeFileIOVolAdapter(v_drv.FileIOVolumeAdapter):
|
||||
"""Subclass for FileIOVolumeAdapter, since it is abstract."""
|
||||
|
||||
def __init__(self, adapter, host_uuid, instance, connection_info,
|
||||
stg_ftsk=None):
|
||||
super(FakeFileIOVolAdapter, self).__init__(
|
||||
adapter, host_uuid, instance, connection_info, stg_ftsk=stg_ftsk)
|
||||
|
||||
def _get_path(self):
|
||||
return "fake_path"
|
||||
|
||||
|
||||
class TestFileIOVolumeAdapter(test_vol.TestVolumeAdapter):
|
||||
"""Tests the FileIOVolumeAdapter. NovaLink is a I/O host."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestFileIOVolumeAdapter, self).setUp()
|
||||
|
||||
# Needed for the volume adapter
|
||||
self.adpt = self.useFixture(pvm_fx.AdapterFx()).adpt
|
||||
mock_inst = mock.MagicMock(uuid='2BC123')
|
||||
|
||||
self.vol_drv = FakeFileIOVolAdapter(self.adpt, 'host_uuid', mock_inst,
|
||||
None)
|
||||
|
||||
self.mock_vio_task = mock.MagicMock()
|
||||
self.mock_stg_ftsk = mock.MagicMock(
|
||||
wrapper_tasks={'vios_uuid': self.mock_vio_task})
|
||||
self.vol_drv.stg_ftsk = self.mock_stg_ftsk
|
||||
|
||||
def test_min_xags(self):
|
||||
"""Ensures xag's only returns SCSI Mappings."""
|
||||
self.assertEqual([pvm_const.XAG.VIO_SMAP], self.vol_drv.min_xags())
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_mgmt_partition')
|
||||
@mock.patch('pypowervm.wrappers.storage.FileIO.bld')
|
||||
def test_connect_volume(self, mock_file_bld, mock_get_mgmt_partition):
|
||||
# Mockups
|
||||
mock_file = mock.Mock()
|
||||
mock_file_bld.return_value = mock_file
|
||||
mock_slot_mgr = mock.MagicMock()
|
||||
|
||||
mock_vios = mock.Mock(uuid='vios_uuid')
|
||||
mock_get_mgmt_partition.return_value = mock_vios
|
||||
|
||||
# Invoke
|
||||
self.vol_drv._connect_volume(mock_slot_mgr)
|
||||
|
||||
# Validate
|
||||
mock_file_bld.assert_called_once_with(self.adpt, 'fake_path')
|
||||
|
||||
@mock.patch('pypowervm.tasks.partition.get_mgmt_partition')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.gen_match_func')
|
||||
@mock.patch('pypowervm.tasks.scsi_mapper.find_maps')
|
||||
def test_disconnect_volume(self, mock_find_maps, mock_gen_match_func,
|
||||
mock_get_mgmt_partition):
|
||||
# Mockups
|
||||
mock_slot_mgr = mock.MagicMock()
|
||||
|
||||
mock_vios = mock.Mock(uuid='vios_uuid')
|
||||
mock_get_mgmt_partition.return_value = mock_vios
|
||||
|
||||
mock_match_func = mock.Mock()
|
||||
mock_gen_match_func.return_value = mock_match_func
|
||||
|
||||
# Invoke
|
||||
self.vol_drv._disconnect_volume(mock_slot_mgr)
|
||||
|
||||
# Validate
|
||||
mock_gen_match_func.assert_called_once_with(
|
||||
pvm_stg.FileIO, names=['fake_path'])
|
||||
mock_find_maps.assert_called_once_with(
|
||||
mock_vios.scsi_mappings, client_lpar_id='2BC123',
|
||||
match_func=mock_match_func)
|
40
nova_powervm/tests/virt/powervm/volume/test_gpfs.py
Normal file
40
nova_powervm/tests/virt/powervm/volume/test_gpfs.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2017 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 mock
|
||||
|
||||
from nova_powervm.tests.virt.powervm.volume import test_driver as test_vol
|
||||
from nova_powervm.virt.powervm.volume import gpfs as v_drv
|
||||
|
||||
|
||||
class TestGPFSVolumeAdapter(test_vol.TestVolumeAdapter):
|
||||
"""Tests the GPFSVolumeAdapter. NovaLink is a I/O host."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestGPFSVolumeAdapter, self).setUp()
|
||||
|
||||
# Needed for the volume adapter
|
||||
self.adpt = mock.Mock()
|
||||
mock_inst = mock.MagicMock(uuid='2BC123')
|
||||
|
||||
# Connection Info
|
||||
mock_conn_info = {'data': {'device_path': '/gpfs/path'}}
|
||||
|
||||
self.vol_drv = v_drv.GPFSVolumeAdapter(
|
||||
self.adpt, 'host_uuid', mock_inst, mock_conn_info)
|
||||
|
||||
def test_get_path(self):
|
||||
self.assertEqual('/gpfs/path', self.vol_drv._get_path())
|
40
nova_powervm/tests/virt/powervm/volume/test_local.py
Normal file
40
nova_powervm/tests/virt/powervm/volume/test_local.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2017 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 mock
|
||||
|
||||
from nova_powervm.tests.virt.powervm.volume import test_driver as test_vol
|
||||
from nova_powervm.virt.powervm.volume import local as v_drv
|
||||
|
||||
|
||||
class TestLocalVolumeAdapter(test_vol.TestVolumeAdapter):
|
||||
"""Tests the LocalVolumeAdapter. NovaLink is a I/O host."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLocalVolumeAdapter, self).setUp()
|
||||
|
||||
# Needed for the volume adapter
|
||||
self.adpt = mock.Mock()
|
||||
mock_inst = mock.MagicMock(uuid='2BC123')
|
||||
|
||||
# Connection Info
|
||||
mock_conn_info = {'data': {'device_path': '/local/path'}}
|
||||
|
||||
self.vol_drv = v_drv.LocalVolumeAdapter(
|
||||
self.adpt, 'host_uuid', mock_inst, mock_conn_info)
|
||||
|
||||
def test_get_path(self):
|
||||
self.assertEqual('/local/path', self.vol_drv._get_path())
|
40
nova_powervm/tests/virt/powervm/volume/test_nfs.py
Normal file
40
nova_powervm/tests/virt/powervm/volume/test_nfs.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2017 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 mock
|
||||
|
||||
from nova_powervm.tests.virt.powervm.volume import test_driver as test_vol
|
||||
from nova_powervm.virt.powervm.volume import nfs as v_drv
|
||||
|
||||
|
||||
class TestNFSVolumeAdapter(test_vol.TestVolumeAdapter):
|
||||
"""Tests the NFSVolumeAdapter. NovaLink is a I/O host."""
|
||||
|
||||
def setUp(self):
|
||||
super(TestNFSVolumeAdapter, self).setUp()
|
||||
|
||||
# Needed for the volume adapter
|
||||
self.adpt = mock.Mock()
|
||||
mock_inst = mock.MagicMock(uuid='2BC123')
|
||||
|
||||
# Connection Info
|
||||
mock_conn_info = {'data': {'export': '/nfs', 'name': 'path'}}
|
||||
|
||||
self.vol_drv = v_drv.NFSVolumeAdapter(
|
||||
self.adpt, 'host_uuid', mock_inst, mock_conn_info)
|
||||
|
||||
def test_get_path(self):
|
||||
self.assertEqual('/nfs/path', self.vol_drv._get_path())
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2014, 2016 IBM Corp.
|
||||
# Copyright 2014, 2017 IBM Corp.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -73,16 +73,6 @@ from nova_powervm.virt.powervm import volume as vol_attach
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
# Defines, for all cinder volume types, which volume driver to use. Currently
|
||||
# only supports Fibre Channel, which has multiple options for connections, and
|
||||
# iSCSI.
|
||||
VOLUME_DRIVER_MAPPINGS = {
|
||||
'fibre_channel': vol_attach.FC_STRATEGY_MAPPING[
|
||||
CONF.powervm.fc_attach_strategy.lower()],
|
||||
'iscsi': vol_attach.NETWORK_STRATEGY_MAPPING[
|
||||
CONF.powervm.network_attach_strategy.lower()],
|
||||
}
|
||||
|
||||
DISK_ADPT_NS = 'nova_powervm.virt.powervm.disk'
|
||||
DISK_ADPT_MAPPINGS = {
|
||||
'localdisk': 'localdisk.LocalStorage',
|
||||
@ -752,8 +742,8 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
# Determine if there are volumes to connect. If so, add a connection
|
||||
# for each type.
|
||||
slot_mgr = slot.build_slot_mgr(instance, self.store_api)
|
||||
vol_drv = self._get_inst_vol_adpt(context, instance,
|
||||
conn_info=connection_info)
|
||||
vol_drv = vol_attach.build_volume_driver(
|
||||
self.adapter, self.host_uuid, instance, connection_info)
|
||||
flow.add(tf_stg.ConnectVolume(vol_drv, slot_mgr))
|
||||
|
||||
# Save the new slot info
|
||||
@ -776,8 +766,8 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
self._log_operation('detach_volume', instance)
|
||||
|
||||
# Get a volume adapter for this volume
|
||||
vol_drv = self._get_inst_vol_adpt(ctx.get_admin_context(), instance,
|
||||
conn_info=connection_info)
|
||||
vol_drv = vol_attach.build_volume_driver(
|
||||
self.adapter, self.host_uuid, instance, connection_info)
|
||||
|
||||
# Before attempting to detach a volume, ensure the instance exists
|
||||
# If a live migration fails, the compute manager will call detach
|
||||
@ -1129,22 +1119,14 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
}
|
||||
|
||||
"""
|
||||
# The host ID
|
||||
connector = {'host': CONF.host}
|
||||
|
||||
# Get the contents from the volume driver
|
||||
vol_drv = self._get_inst_vol_adpt(ctx.get_admin_context(),
|
||||
instance)
|
||||
if vol_drv is not None:
|
||||
|
||||
if CONF.powervm.volume_adapter.lower() == "fibre_channel":
|
||||
# Set the WWPNs
|
||||
wwpn_list = vol_drv.wwpns()
|
||||
if wwpn_list is not None:
|
||||
connector["wwpns"] = wwpn_list
|
||||
connector['host'] = vol_drv.host_name()
|
||||
connector['initiator'] = vol_attach.get_iscsi_initiator(
|
||||
self.adapter)
|
||||
# Put the values in the connector
|
||||
connector = {}
|
||||
wwpn_list = vol_attach.get_wwpns_for_volume_connector(
|
||||
self.adapter, self.host_uuid, instance)
|
||||
if wwpn_list is not None:
|
||||
connector["wwpns"] = wwpn_list
|
||||
connector['host'] = vol_attach.get_hostname_for_volume(instance)
|
||||
connector['initiator'] = vol_attach.get_iscsi_initiator(self.adapter)
|
||||
return connector
|
||||
|
||||
def migrate_disk_and_power_off(self, context, instance, dest,
|
||||
@ -1720,9 +1702,9 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
if not conn_info:
|
||||
continue
|
||||
|
||||
vol_drv = self._get_inst_vol_adpt(context, instance,
|
||||
conn_info=conn_info,
|
||||
stg_ftsk=stg_ftsk)
|
||||
vol_drv = vol_attach.build_volume_driver(
|
||||
self.adapter, self.host_uuid, instance, conn_info,
|
||||
stg_ftsk=stg_ftsk)
|
||||
yield bdm, vol_drv
|
||||
|
||||
def _build_vol_drivers(self, context, instance, block_device_info=None,
|
||||
@ -1836,51 +1818,20 @@ class PowerVMDriver(driver.ComputeDriver):
|
||||
# All operations for deploy/destroy require scsi by default. This is
|
||||
# either vopt, local/SSP disks, etc...
|
||||
xags = {pvm_const.XAG.VIO_SMAP}
|
||||
if not bdms:
|
||||
LOG.debug('Instance XAGs for VM %(inst)s is %(xags)s.',
|
||||
{'inst': instance.name,
|
||||
'xags': ','.join(xags)})
|
||||
return list(xags)
|
||||
|
||||
# BDMs could be none, if there are no cinder volumes.
|
||||
bdms = bdms if bdms else []
|
||||
|
||||
# If we have any volumes, add the volumes required mapping XAGs.
|
||||
adp_type = VOLUME_DRIVER_MAPPINGS[CONF.powervm.volume_adapter]
|
||||
vol_cls = importutils.import_class(adp_type)
|
||||
xags.update(set(vol_cls.min_xags()))
|
||||
for bdm in bdms:
|
||||
driver_type = bdm.get('connection_info').get('driver_volume_type')
|
||||
vol_cls = vol_attach.get_volume_class(driver_type)
|
||||
xags.update(set(vol_cls.min_xags()))
|
||||
|
||||
LOG.debug('Instance XAGs for VM %(inst)s is %(xags)s.',
|
||||
{'inst': instance.name,
|
||||
'xags': ','.join(xags)})
|
||||
{'inst': instance.name, 'xags': ','.join(xags)})
|
||||
return list(xags)
|
||||
|
||||
def _get_inst_vol_adpt(self, context, instance, conn_info=None,
|
||||
stg_ftsk=None):
|
||||
"""Returns the appropriate volume driver based on connection type.
|
||||
|
||||
Checks the connection info for connection-type and return the
|
||||
connector, if no connection info is provided returns the default
|
||||
connector.
|
||||
:param context: security context
|
||||
:param instance: Nova instance for which the volume adapter is needed.
|
||||
:param conn_info: BDM connection information of the instance to
|
||||
get the volume adapter type (vSCSI/NPIV) requested.
|
||||
:param stg_ftsk: (Optional) The FeedTask that can be used to defer the
|
||||
mapping actions against the Virtual I/O Server for. If
|
||||
not provided, then the connect/disconnect actions will
|
||||
be immediate.
|
||||
:return: Returns the volume adapter, if conn_info is not passed then
|
||||
returns the volume adapter based on the CONF
|
||||
fc_attach_strategy property (npiv/vscsi). Otherwise returns
|
||||
the adapter based on the connection-type of
|
||||
connection_info.
|
||||
"""
|
||||
adp_type = VOLUME_DRIVER_MAPPINGS[CONF.powervm.volume_adapter]
|
||||
vol_cls = importutils.import_class(adp_type)
|
||||
if conn_info:
|
||||
LOG.debug('Volume Adapter returned for connection_info=%s',
|
||||
conn_info)
|
||||
LOG.debug('Volume Adapter class %(cls)s for instance %(inst)s',
|
||||
{'cls': vol_cls.__name__, 'inst': instance.name})
|
||||
return vol_cls(self.adapter, self.host_uuid,
|
||||
instance, conn_info, stg_ftsk=stg_ftsk)
|
||||
|
||||
def _get_boot_connectivity_type(self, context, bdms, block_device_info):
|
||||
"""Get connectivity information for the instance.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright 2015, 2016 IBM Corp.
|
||||
# Copyright 2015, 2017 IBM Corp.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -20,7 +20,11 @@ from pypowervm.tasks import partition
|
||||
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
||||
|
||||
# Defines the various volume connectors that can be used.
|
||||
from nova import exception
|
||||
from oslo_utils import importutils
|
||||
|
||||
from nova_powervm import conf as cfg
|
||||
from nova_powervm.virt.powervm.i18n import _
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
@ -28,10 +32,57 @@ FC_STRATEGY_MAPPING = {
|
||||
'npiv': CONF.powervm.fc_npiv_adapter_api,
|
||||
'vscsi': CONF.powervm.fc_vscsi_adapter_api
|
||||
}
|
||||
NETWORK_STRATEGY_MAPPING = {
|
||||
'iscsi': 'nova_powervm.virt.powervm.volume.iscsi.IscsiVolumeAdapter'
|
||||
|
||||
_STATIC_VOLUME_MAPPINGS = {
|
||||
'iscsi': 'nova_powervm.virt.powervm.volume.iscsi.'
|
||||
'IscsiVolumeAdapter',
|
||||
'local': 'nova_powervm.virt.powervm.volume.local.'
|
||||
'LocalVolumeAdapter',
|
||||
'nfs': 'nova_powervm.virt.powervm.volume.nfs.NFSVolumeAdapter',
|
||||
'gpfs': 'nova_powervm.virt.powervm.volume.gpfs.GPFSVolumeAdapter',
|
||||
}
|
||||
|
||||
|
||||
def build_volume_driver(adapter, host_uuid, instance, conn_info,
|
||||
stg_ftsk=None):
|
||||
vol_cls = get_volume_class(conn_info.get('driver_volume_type'))
|
||||
|
||||
return vol_cls(adapter, host_uuid, instance, conn_info,
|
||||
stg_ftsk=stg_ftsk)
|
||||
|
||||
|
||||
def get_volume_class(drv_type):
|
||||
if drv_type in _STATIC_VOLUME_MAPPINGS:
|
||||
class_type = _STATIC_VOLUME_MAPPINGS[drv_type]
|
||||
elif drv_type == 'fibre_channel':
|
||||
class_type = (FC_STRATEGY_MAPPING[
|
||||
CONF.powervm.fc_attach_strategy.lower()])
|
||||
else:
|
||||
failure_reason = _("Invalid connection type of %s") % drv_type
|
||||
raise exception.InvalidVolume(reason=failure_reason)
|
||||
|
||||
return importutils.import_class(class_type)
|
||||
|
||||
|
||||
def get_hostname_for_volume(instance):
|
||||
if CONF.powervm.fc_attach_strategy.lower() == 'npiv':
|
||||
# Tie the host name to the instance, as it will be represented in
|
||||
# the backend as a full server.
|
||||
host = CONF.host if len(CONF.host) < 20 else CONF.host[:20]
|
||||
return host + '_' + instance.name
|
||||
else:
|
||||
return CONF.host
|
||||
|
||||
|
||||
def get_wwpns_for_volume_connector(adapter, host_uuid, instance):
|
||||
# WWPNs are derived from the FC connector. Pass in a fake connection info
|
||||
# to trick it into thinking it FC
|
||||
fake_fc_conn_info = {'driver_volume_type': 'fibre_channel'}
|
||||
fc_vol_drv = build_volume_driver(adapter, host_uuid, instance,
|
||||
fake_fc_conn_info)
|
||||
return fc_vol_drv.wwpns()
|
||||
|
||||
|
||||
_ISCSI_INITIATOR = None
|
||||
_ISCSI_LOOKUP_COMPLETE = False
|
||||
|
||||
|
@ -265,10 +265,3 @@ class FibreChannelVolumeAdapter(PowerVMVolumeAdapter):
|
||||
:return: The list of WWPNs that need to be included in the zone set.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def host_name(self):
|
||||
"""Derives the host name that should be used for the storage device.
|
||||
|
||||
:return: The host name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
97
nova_powervm/virt/powervm/volume/fileio.py
Normal file
97
nova_powervm/virt/powervm/volume/fileio.py
Normal file
@ -0,0 +1,97 @@
|
||||
# Copyright 2017 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 abc
|
||||
import six
|
||||
|
||||
from nova_powervm import conf as cfg
|
||||
from nova_powervm.virt.powervm.i18n import _LI
|
||||
from nova_powervm.virt.powervm.volume import driver as v_driver
|
||||
from oslo_log import log as logging
|
||||
from pypowervm import const as pvm_const
|
||||
from pypowervm.tasks import partition
|
||||
from pypowervm.tasks import scsi_mapper as tsk_map
|
||||
from pypowervm.wrappers import storage as pvm_stg
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class FileIOVolumeAdapter(v_driver.PowerVMVolumeAdapter):
|
||||
"""Base class for connecting file based Cinder Volumes to PowerVM VMs."""
|
||||
|
||||
@classmethod
|
||||
def min_xags(cls):
|
||||
return [pvm_const.XAG.VIO_SMAP]
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_path(self):
|
||||
"""Return the path to the file to connect."""
|
||||
pass
|
||||
|
||||
def _connect_volume(self, slot_mgr):
|
||||
# Get the hosting UUID
|
||||
nl_vios_wrap = partition.get_mgmt_partition(self.adapter)
|
||||
vios_uuid = nl_vios_wrap.uuid
|
||||
|
||||
# Get the File Path
|
||||
fio = pvm_stg.FileIO.bld(self.adapter, self._get_path())
|
||||
|
||||
def add_func(vios_w):
|
||||
# If the vios doesn't match, just return
|
||||
if vios_w.uuid != vios_uuid:
|
||||
return None
|
||||
|
||||
LOG.info(_LI("Adding logical volume disk connection between VM "
|
||||
"%(vm)s and VIOS %(vios)s."),
|
||||
{'vm': self.instance.name, 'vios': vios_w.name},
|
||||
instance=self.instance)
|
||||
mapping = tsk_map.build_vscsi_mapping(
|
||||
self.host_uuid, vios_w, self.vm_uuid, fio)
|
||||
return tsk_map.add_map(vios_w, mapping)
|
||||
|
||||
self.stg_ftsk.add_functor_subtask(add_func)
|
||||
|
||||
def _disconnect_volume(self, slot_mgr):
|
||||
# Get the hosting UUID
|
||||
nl_vios_wrap = partition.get_mgmt_partition(self.adapter)
|
||||
vios_uuid = nl_vios_wrap.uuid
|
||||
|
||||
# Build the match function
|
||||
match_func = tsk_map.gen_match_func(pvm_stg.FileIO,
|
||||
names=[self._get_path()])
|
||||
|
||||
# Make sure the remove function will run within the transaction manager
|
||||
def rm_func(vios_w):
|
||||
# If the vios doesn't match, just return
|
||||
if vios_w.uuid != vios_uuid:
|
||||
return None
|
||||
|
||||
LOG.info(_LI("Disconnecting instance %(inst)s from storage "
|
||||
"disks."), {'inst': self.instance.name},
|
||||
instance=self.instance)
|
||||
return tsk_map.remove_maps(vios_w, self.vm_uuid,
|
||||
match_func=match_func)
|
||||
|
||||
self.stg_ftsk.add_functor_subtask(rm_func)
|
||||
|
||||
# Find the disk directly.
|
||||
mappings = tsk_map.find_maps(nl_vios_wrap.scsi_mappings,
|
||||
client_lpar_id=self.vm_uuid,
|
||||
match_func=match_func)
|
||||
|
||||
return [x.backing_storage for x in mappings]
|
24
nova_powervm/virt/powervm/volume/gpfs.py
Normal file
24
nova_powervm/virt/powervm/volume/gpfs.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
from nova_powervm.virt.powervm.volume import fileio
|
||||
|
||||
|
||||
class GPFSVolumeAdapter(fileio.FileIOVolumeAdapter):
|
||||
"""Connects GPFS Cinder Volumes to PowerVM VMs."""
|
||||
|
||||
def _get_path(self):
|
||||
return self.connection_info.get("data")['device_path']
|
@ -56,9 +56,6 @@ class IscsiVolumeAdapter(volume.VscsiVolumeAdapter,
|
||||
"""The type of volume supported by this type."""
|
||||
return 'iscsi'
|
||||
|
||||
def host_name(self):
|
||||
return CONF.host
|
||||
|
||||
@classmethod
|
||||
def min_xags(cls):
|
||||
"""List of pypowervm XAGs needed to support this adapter."""
|
||||
|
24
nova_powervm/virt/powervm/volume/local.py
Normal file
24
nova_powervm/virt/powervm/volume/local.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
from nova_powervm.virt.powervm.volume import fileio
|
||||
|
||||
|
||||
class LocalVolumeAdapter(fileio.FileIOVolumeAdapter):
|
||||
"""Connects Local Cinder Volumes to PowerVM VMs."""
|
||||
|
||||
def _get_path(self):
|
||||
return self.connection_info['data']['device_path']
|
26
nova_powervm/virt/powervm/volume/nfs.py
Normal file
26
nova_powervm/virt/powervm/volume/nfs.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Copyright 2017 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.
|
||||
|
||||
from nova_powervm.virt.powervm.volume import fileio
|
||||
import os
|
||||
|
||||
|
||||
class NFSVolumeAdapter(fileio.FileIOVolumeAdapter):
|
||||
"""Connects NFS Cinder Volumes to PowerVM VMs."""
|
||||
|
||||
def _get_path(self):
|
||||
return os.path.join(self.connection_info['data']['export'],
|
||||
self.connection_info['data']['name'])
|
@ -592,14 +592,6 @@ class NPIVVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
||||
"%(fabric)s."), {'fabric': fabric},
|
||||
instance=self.instance)
|
||||
|
||||
def host_name(self):
|
||||
"""Derives the host name that should be used for the storage device.
|
||||
|
||||
:return: The host name.
|
||||
"""
|
||||
host = CONF.host if len(CONF.host) < 20 else CONF.host[:20]
|
||||
return host + '_' + self.instance.name
|
||||
|
||||
def _set_fabric_state(self, fabric, state):
|
||||
"""Sets the fabric state into the instance's system metadata.
|
||||
|
||||
|
@ -355,13 +355,6 @@ class PVVscsiFCVolumeAdapter(volume.VscsiVolumeAdapter,
|
||||
_vscsi_pfc_wwpns = pvm_tpar.get_physical_wwpns(self.adapter)
|
||||
return _vscsi_pfc_wwpns
|
||||
|
||||
def host_name(self):
|
||||
"""Derives the host name that should be used for the storage device.
|
||||
|
||||
:return: The host name.
|
||||
"""
|
||||
return CONF.host
|
||||
|
||||
def _get_hdisk_itls(self, vios_w):
|
||||
"""Returns the mapped ITLs for the hdisk for the given VIOS.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user