Merge "Replace the NetApp driver proxy layer with a proper factory."
This commit is contained in:
commit
c89875d1dc
|
@ -258,6 +258,7 @@ class TestCase(testtools.TestCase):
|
||||||
patcher = mock.patch.object(obj, attr_name, new_attr, **kwargs)
|
patcher = mock.patch.object(obj, attr_name, new_attr, **kwargs)
|
||||||
patcher.start()
|
patcher.start()
|
||||||
self.addCleanup(patcher.stop)
|
self.addCleanup(patcher.stop)
|
||||||
|
return new_attr
|
||||||
|
|
||||||
# Useful assertions
|
# Useful assertions
|
||||||
def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
|
def assertDictMatch(self, d1, d2, approx_equal=False, tolerance=0.001):
|
||||||
|
|
|
@ -745,21 +745,6 @@ class NetAppDriverNegativeTestCase(test.TestCase):
|
||||||
except exception.InvalidInput:
|
except exception.InvalidInput:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_non_netapp_driver(self):
|
|
||||||
self.mock_object(utils, 'OpenStackInfo')
|
|
||||||
configuration = create_configuration()
|
|
||||||
common.netapp_unified_plugin_registry['test_family'] =\
|
|
||||||
{'iscsi': 'cinder.volume.drivers.arbitrary.IscsiDriver'}
|
|
||||||
configuration.netapp_storage_family = 'test_family'
|
|
||||||
configuration.netapp_storage_protocol = 'iscsi'
|
|
||||||
try:
|
|
||||||
common.NetAppDriver(configuration=configuration)
|
|
||||||
raise AssertionError('Non NetApp driver is getting instantiated.')
|
|
||||||
except exception.InvalidInput:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
common.netapp_unified_plugin_registry.pop('test_family')
|
|
||||||
|
|
||||||
|
|
||||||
class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
|
class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
|
||||||
"""HTTP handler that fakes enough stuff to allow the driver to run."""
|
"""HTTP handler that fakes enough stuff to allow the driver to run."""
|
||||||
|
@ -1213,8 +1198,8 @@ class NetAppDirect7modeISCSIDriverTestCase_NV(
|
||||||
self.driver.volume_list = []
|
self.driver.volume_list = []
|
||||||
|
|
||||||
def test_connect(self):
|
def test_connect(self):
|
||||||
self.driver.driver.library.zapi_client = mock.MagicMock()
|
self.driver.library.zapi_client = mock.MagicMock()
|
||||||
self.driver.driver.library.zapi_client.get_ontapi_version.\
|
self.driver.library.zapi_client.get_ontapi_version.\
|
||||||
return_value = (1, 20)
|
return_value = (1, 20)
|
||||||
self.driver.check_for_setup_error()
|
self.driver.check_for_setup_error()
|
||||||
|
|
||||||
|
|
|
@ -853,10 +853,10 @@ class NetAppEseriesISCSIDriverTestCase(test.TestCase):
|
||||||
fake_pool['volumeGroupRef'] = 'foo'
|
fake_pool['volumeGroupRef'] = 'foo'
|
||||||
fake_pools = [fake_pool]
|
fake_pools = [fake_pool]
|
||||||
storage_pools.return_value = fake_pools
|
storage_pools.return_value = fake_pools
|
||||||
drv = self.driver
|
storage_vol = self.driver._create_volume(
|
||||||
storage_vol = drv.driver._create_volume(self.fake_eseries_pool_label,
|
self.fake_eseries_pool_label,
|
||||||
self.fake_eseries_volume_label,
|
self.fake_eseries_volume_label,
|
||||||
self.fake_size_gb)
|
self.fake_size_gb)
|
||||||
log_info.assert_called_once_with("Created volume with label %s.",
|
log_info.assert_called_once_with("Created volume with label %s.",
|
||||||
self.fake_eseries_volume_label)
|
self.fake_eseries_volume_label)
|
||||||
self.assertEqual('CorrectVolume', storage_vol)
|
self.assertEqual('CorrectVolume', storage_vol)
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
# Copyright (c) 2014 Clinton Knight. 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
|
||||||
|
import six
|
||||||
|
|
||||||
|
from cinder import exception
|
||||||
|
from cinder import test
|
||||||
|
import cinder.tests.volume.drivers.netapp.fakes as na_fakes
|
||||||
|
import cinder.volume.drivers.netapp.common as na_common
|
||||||
|
import cinder.volume.drivers.netapp.dataontap.fc_cmode as fc_cmode
|
||||||
|
import cinder.volume.drivers.netapp.utils as na_utils
|
||||||
|
|
||||||
|
|
||||||
|
class NetAppDriverFactoryTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(NetAppDriverFactoryTestCase, self).setUp()
|
||||||
|
self.mock_object(na_common, 'LOG')
|
||||||
|
|
||||||
|
def test_new(self):
|
||||||
|
|
||||||
|
self.mock_object(na_utils.OpenStackInfo, 'info',
|
||||||
|
mock.Mock(return_value='fake_info'))
|
||||||
|
mock_create_driver = self.mock_object(na_common.NetAppDriver,
|
||||||
|
'create_driver')
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.netapp_storage_family = 'fake_family'
|
||||||
|
config.netapp_storage_protocol = 'fake_protocol'
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
na_common.NetAppDriver(**kwargs)
|
||||||
|
|
||||||
|
kwargs['app_version'] = 'fake_info'
|
||||||
|
mock_create_driver.assert_called_with('fake_family', 'fake_protocol',
|
||||||
|
*(), **kwargs)
|
||||||
|
|
||||||
|
def test_new_missing_config(self):
|
||||||
|
|
||||||
|
self.mock_object(na_utils.OpenStackInfo, 'info')
|
||||||
|
self.mock_object(na_common.NetAppDriver, 'create_driver')
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidInput, na_common.NetAppDriver, **{})
|
||||||
|
|
||||||
|
def test_new_missing_family(self):
|
||||||
|
|
||||||
|
self.mock_object(na_utils.OpenStackInfo, 'info')
|
||||||
|
self.mock_object(na_common.NetAppDriver, 'create_driver')
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.netapp_storage_protocol = 'fake_protocol'
|
||||||
|
config.netapp_storage_family = None
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def test_new_missing_protocol(self):
|
||||||
|
|
||||||
|
self.mock_object(na_utils.OpenStackInfo, 'info')
|
||||||
|
self.mock_object(na_common.NetAppDriver, 'create_driver')
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.netapp_storage_protocol = None
|
||||||
|
config.netapp_storage_family = 'fake_family'
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def test_create_driver(self):
|
||||||
|
|
||||||
|
def get_full_class_name(obj):
|
||||||
|
return obj.__module__ + '.' + obj.__class__.__name__
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration(),
|
||||||
|
'app_version': 'fake_info'}
|
||||||
|
|
||||||
|
registry = na_common.NETAPP_UNIFIED_DRIVER_REGISTRY
|
||||||
|
|
||||||
|
for family in six.iterkeys(registry):
|
||||||
|
for protocol, full_class_name in six.iteritems(registry[family]):
|
||||||
|
driver = na_common.NetAppDriver.create_driver(
|
||||||
|
family, protocol, **kwargs)
|
||||||
|
self.assertEqual(full_class_name, get_full_class_name(driver))
|
||||||
|
|
||||||
|
def test_create_driver_case_insensitive(self):
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration(),
|
||||||
|
'app_version': 'fake_info'}
|
||||||
|
|
||||||
|
driver = na_common.NetAppDriver.create_driver('ONTAP_CLUSTER', 'FC',
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
self.assertIsInstance(driver, fc_cmode.NetAppCmodeFibreChannelDriver)
|
||||||
|
|
||||||
|
def test_create_driver_invalid_family(self):
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration(),
|
||||||
|
'app_version': 'fake_info'}
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver.create_driver,
|
||||||
|
'kardashian', 'iscsi', **kwargs)
|
||||||
|
|
||||||
|
def test_create_driver_invalid_protocol(self):
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration(),
|
||||||
|
'app_version': 'fake_info'}
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver.create_driver,
|
||||||
|
'ontap_7mode', 'carrier_pigeon', **kwargs)
|
|
@ -1,4 +1,3 @@
|
||||||
# Copyright (c) 2012 NetApp, Inc. All rights reserved.
|
|
||||||
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
# Copyright (c) 2014 Navneet Singh. All rights reserved.
|
||||||
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
# Copyright (c) 2014 Clinton Knight. All rights reserved.
|
||||||
#
|
#
|
||||||
|
@ -16,162 +15,98 @@
|
||||||
"""
|
"""
|
||||||
Unified driver for NetApp storage systems.
|
Unified driver for NetApp storage systems.
|
||||||
|
|
||||||
Supports call to multiple storage systems of different families and protocols.
|
Supports multiple storage systems of different families and protocols.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from oslo.utils import importutils
|
from oslo.utils import importutils
|
||||||
|
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.i18n import _, _LI
|
from cinder.i18n import _, _LI
|
||||||
from cinder.openstack.common import log as logging
|
from cinder.openstack.common import log as logging
|
||||||
from cinder.volume import driver
|
|
||||||
from cinder.volume.drivers.netapp.options import netapp_proxy_opts
|
from cinder.volume.drivers.netapp.options import netapp_proxy_opts
|
||||||
from cinder.volume.drivers.netapp import utils as na_utils
|
from cinder.volume.drivers.netapp import utils as na_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# NOTE(singn): Holds family:{protocol:driver} registration information.
|
|
||||||
# Plug in new families and protocols to support new drivers.
|
|
||||||
# No other code modification required.
|
|
||||||
|
|
||||||
DATAONTAP_PATH = 'cinder.volume.drivers.netapp.dataontap'
|
DATAONTAP_PATH = 'cinder.volume.drivers.netapp.dataontap'
|
||||||
ESERIES_PATH = 'cinder.volume.drivers.netapp.eseries'
|
ESERIES_PATH = 'cinder.volume.drivers.netapp.eseries'
|
||||||
|
|
||||||
netapp_unified_plugin_registry =\
|
# Add new drivers here, no other code changes required.
|
||||||
{'ontap_cluster':
|
NETAPP_UNIFIED_DRIVER_REGISTRY = {
|
||||||
{
|
'ontap_cluster':
|
||||||
'iscsi': DATAONTAP_PATH + '.iscsi_cmode.NetAppCmodeISCSIDriver',
|
{
|
||||||
'nfs': DATAONTAP_PATH + '.nfs_cmode.NetAppCmodeNfsDriver',
|
'iscsi': DATAONTAP_PATH + '.iscsi_cmode.NetAppCmodeISCSIDriver',
|
||||||
'fc': DATAONTAP_PATH + '.fc_cmode.NetAppCmodeFibreChannelDriver'
|
'nfs': DATAONTAP_PATH + '.nfs_cmode.NetAppCmodeNfsDriver',
|
||||||
},
|
'fc': DATAONTAP_PATH + '.fc_cmode.NetAppCmodeFibreChannelDriver'
|
||||||
'ontap_7mode':
|
},
|
||||||
{
|
'ontap_7mode':
|
||||||
'iscsi': DATAONTAP_PATH + '.iscsi_7mode.NetApp7modeISCSIDriver',
|
{
|
||||||
'nfs': DATAONTAP_PATH + '.nfs_7mode.NetApp7modeNfsDriver',
|
'iscsi': DATAONTAP_PATH + '.iscsi_7mode.NetApp7modeISCSIDriver',
|
||||||
'fc': DATAONTAP_PATH + '.fc_7mode.NetApp7modeFibreChannelDriver'
|
'nfs': DATAONTAP_PATH + '.nfs_7mode.NetApp7modeNfsDriver',
|
||||||
},
|
'fc': DATAONTAP_PATH + '.fc_7mode.NetApp7modeFibreChannelDriver'
|
||||||
'eseries':
|
},
|
||||||
{
|
'eseries':
|
||||||
'iscsi': ESERIES_PATH + '.iscsi.NetAppEseriesISCSIDriver'
|
{
|
||||||
},
|
'iscsi': ESERIES_PATH + '.iscsi.NetAppEseriesISCSIDriver'
|
||||||
}
|
}}
|
||||||
|
|
||||||
|
|
||||||
class NetAppDriver(object):
|
class NetAppDriver(object):
|
||||||
""""NetApp unified block storage driver.
|
""""NetApp unified block storage driver.
|
||||||
|
|
||||||
Acts as a mediator to NetApp storage drivers.
|
Acts as a factory to create NetApp storage drivers based on the
|
||||||
Proxies requests based on the storage family and protocol configured.
|
storage family and protocol configured.
|
||||||
Override the proxy driver method by adding method in this driver.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
REQUIRED_FLAGS = ['netapp_storage_family', 'netapp_storage_protocol']
|
REQUIRED_FLAGS = ['netapp_storage_family', 'netapp_storage_protocol']
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
super(NetAppDriver, self).__init__()
|
|
||||||
|
config = kwargs.get('configuration', None)
|
||||||
|
if not config:
|
||||||
|
raise exception.InvalidInput(
|
||||||
|
reason=_('Required configuration not found'))
|
||||||
|
|
||||||
|
config.append_config_values(netapp_proxy_opts)
|
||||||
|
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
||||||
|
|
||||||
app_version = na_utils.OpenStackInfo().info()
|
app_version = na_utils.OpenStackInfo().info()
|
||||||
LOG.info(_LI('OpenStack OS Version Info: %(info)s') % {
|
LOG.info(_LI('OpenStack OS Version Info: %(info)s') % {
|
||||||
'info': app_version})
|
'info': app_version})
|
||||||
|
|
||||||
self.configuration = kwargs.get('configuration', None)
|
|
||||||
if not self.configuration:
|
|
||||||
raise exception.InvalidInput(
|
|
||||||
reason=_("Required configuration not found"))
|
|
||||||
|
|
||||||
self.configuration.append_config_values(netapp_proxy_opts)
|
|
||||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
|
||||||
|
|
||||||
kwargs['app_version'] = app_version
|
kwargs['app_version'] = app_version
|
||||||
|
|
||||||
self.driver = NetAppDriverFactory.create_driver(
|
return NetAppDriver.create_driver(config.netapp_storage_family,
|
||||||
self.configuration.netapp_storage_family,
|
config.netapp_storage_protocol,
|
||||||
self.configuration.netapp_storage_protocol,
|
*args, **kwargs)
|
||||||
*args, **kwargs)
|
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
|
||||||
"""Sets the attribute."""
|
|
||||||
if getattr(self, 'driver', None):
|
|
||||||
self.driver.__setattr__(name, value)
|
|
||||||
return
|
|
||||||
object.__setattr__(self, name, value)
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
""""Gets the attribute."""
|
|
||||||
drv = object.__getattribute__(self, 'driver')
|
|
||||||
return getattr(drv, name)
|
|
||||||
|
|
||||||
|
|
||||||
class NetAppDriverFactory(object):
|
|
||||||
"""Factory to instantiate appropriate NetApp driver."""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_driver(storage_family, storage_protocol, *args, **kwargs):
|
def create_driver(storage_family, storage_protocol, *args, **kwargs):
|
||||||
""""Creates an appropriate driver based on family and protocol."""
|
""""Creates an appropriate driver based on family and protocol."""
|
||||||
|
|
||||||
fmt = {'storage_family': storage_family.lower(),
|
storage_family = storage_family.lower()
|
||||||
'storage_protocol': storage_protocol.lower()}
|
storage_protocol = storage_protocol.lower()
|
||||||
LOG.info(_LI('Requested unified config: %(storage_family)s and '
|
|
||||||
'%(storage_protocol)s') % fmt)
|
|
||||||
|
|
||||||
family_meta = netapp_unified_plugin_registry.get(storage_family)
|
fmt = {'storage_family': storage_family,
|
||||||
|
'storage_protocol': storage_protocol}
|
||||||
|
LOG.info(_LI('Requested unified config: %(storage_family)s and '
|
||||||
|
'%(storage_protocol)s.') % fmt)
|
||||||
|
|
||||||
|
family_meta = NETAPP_UNIFIED_DRIVER_REGISTRY.get(storage_family)
|
||||||
if family_meta is None:
|
if family_meta is None:
|
||||||
raise exception.InvalidInput(
|
raise exception.InvalidInput(
|
||||||
reason=_('Storage family %s is not supported')
|
reason=_('Storage family %s is not supported.')
|
||||||
% storage_family)
|
% storage_family)
|
||||||
|
|
||||||
driver_loc = family_meta.get(storage_protocol)
|
driver_loc = family_meta.get(storage_protocol)
|
||||||
if driver_loc is None:
|
if driver_loc is None:
|
||||||
raise exception.InvalidInput(
|
raise exception.InvalidInput(
|
||||||
reason=_('Protocol %(storage_protocol)s is not supported'
|
reason=_('Protocol %(storage_protocol)s is not supported '
|
||||||
' for storage family %(storage_family)s') % fmt)
|
'for storage family %(storage_family)s.') % fmt)
|
||||||
|
|
||||||
NetAppDriverFactory.check_netapp_driver(driver_loc)
|
|
||||||
kwargs = kwargs or {}
|
kwargs = kwargs or {}
|
||||||
kwargs['netapp_mode'] = 'proxy'
|
kwargs['netapp_mode'] = 'proxy'
|
||||||
driver = importutils.import_object(driver_loc, *args, **kwargs)
|
driver = importutils.import_object(driver_loc, *args, **kwargs)
|
||||||
LOG.info(_LI('NetApp driver of family %(storage_family)s and protocol'
|
LOG.info(_LI('NetApp driver of family %(storage_family)s and protocol '
|
||||||
' %(storage_protocol)s loaded') % fmt)
|
'%(storage_protocol)s loaded.') % fmt)
|
||||||
return driver
|
return driver
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_netapp_driver(location):
|
|
||||||
"""Checks if the driver requested is a netapp driver."""
|
|
||||||
if location.find(".netapp.") == -1:
|
|
||||||
raise exception.InvalidInput(
|
|
||||||
reason=_("Only loading netapp drivers supported."))
|
|
||||||
|
|
||||||
|
|
||||||
class Deprecated(driver.VolumeDriver):
|
|
||||||
"""Deprecated driver for NetApp.
|
|
||||||
|
|
||||||
This driver is used for mapping deprecated
|
|
||||||
drivers to itself in manager. It prevents cinder
|
|
||||||
from getting errored out in case of upgrade scenarios
|
|
||||||
and also suggests further steps.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self._log_deprecated_warn()
|
|
||||||
|
|
||||||
def _log_deprecated_warn(self):
|
|
||||||
"""Logs appropriate warning and suggestion."""
|
|
||||||
|
|
||||||
link = "https://communities.netapp.com/groups/openstack"
|
|
||||||
msg = _("The configured NetApp driver is deprecated."
|
|
||||||
" Please refer the link to resolve the issue '%s'.")
|
|
||||||
LOG.warning(msg % link)
|
|
||||||
|
|
||||||
def check_for_setup_error(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def ensure_export(self, context, volume):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
|
||||||
"""Return the current state of the volume service. If 'refresh' is
|
|
||||||
True, run the update first.
|
|
||||||
"""
|
|
||||||
self._log_deprecated_warn()
|
|
||||||
return None
|
|
||||||
|
|
Loading…
Reference in New Issue