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:
Drew Thorstensen 2017-01-18 17:04:11 -05:00 committed by Drew Thorstensen (thorst)
parent 8651e2213c
commit ebee3eae88
18 changed files with 561 additions and 140 deletions

View File

@ -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

View File

@ -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 '

View File

@ -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,

View File

@ -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))

View 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)

View 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())

View 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())

View 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())

View File

@ -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.

View File

@ -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

View File

@ -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()

View 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]

View 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']

View File

@ -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."""

View 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']

View 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'])

View File

@ -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.

View File

@ -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.