Add factory for NetApp drivers
As we have done in Cinder, NetApp is planning additional Manila drivers and would like to have a factory to simplify the user experience of selecting a driver in manila.conf. The implementation will be very similar to the Cinder one. Included is the Cinder map in the share manager that allows users to change driver paths/names while maintaining backward compatibility. Implements blueprint netapp-driver-factory Change-Id: I7dd401688e1bcccdde09b3ecf701924769ad2fe8
This commit is contained in:
parent
1d87257f18
commit
afe62eea17
|
@ -42,6 +42,11 @@ from manila import utils
|
||||||
|
|
||||||
|
|
||||||
NETAPP_NAS_OPTS = [
|
NETAPP_NAS_OPTS = [
|
||||||
|
cfg.StrOpt('netapp_storage_family',
|
||||||
|
default='ontap_cluster',
|
||||||
|
help=('The storage family type used on the storage system; '
|
||||||
|
'valid values include ontap_cluster for using '
|
||||||
|
'clustered Data ONTAP.')),
|
||||||
cfg.StrOpt('netapp_nas_login',
|
cfg.StrOpt('netapp_nas_login',
|
||||||
default='admin',
|
default='admin',
|
||||||
help='User name for the ONTAP controller.'),
|
help='User name for the ONTAP controller.'),
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Copyright (c) 2015 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.
|
||||||
|
"""
|
||||||
|
Unified driver for NetApp storage systems.
|
||||||
|
|
||||||
|
Supports multiple storage systems of different families and driver modes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from oslo_utils import importutils
|
||||||
|
|
||||||
|
from manila import exception
|
||||||
|
from manila.i18n import _, _LI
|
||||||
|
from manila.openstack.common import log as logging
|
||||||
|
from manila.share import driver
|
||||||
|
from manila.share.drivers.netapp import cluster_mode
|
||||||
|
from manila.share.drivers.netapp import utils as na_utils
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Add new drivers here, no other code changes required.
|
||||||
|
NETAPP_UNIFIED_DRIVER_REGISTRY = {
|
||||||
|
'ontap_cluster':
|
||||||
|
{
|
||||||
|
'multi_svm':
|
||||||
|
'manila.share.drivers.netapp.cluster_mode.NetAppClusteredShareDriver',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NETAPP_UNIFIED_DRIVER_DEFAULT_MODE = {
|
||||||
|
'ontap_cluster': 'multi_svm',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NetAppDriver(object):
|
||||||
|
""""NetApp unified share storage driver.
|
||||||
|
|
||||||
|
Acts as a factory to create NetApp storage drivers based on the
|
||||||
|
storage family and driver mode configured.
|
||||||
|
"""
|
||||||
|
|
||||||
|
REQUIRED_FLAGS = ['netapp_storage_family', 'driver_handles_share_servers']
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
|
||||||
|
config = kwargs.get('configuration', None)
|
||||||
|
if not config:
|
||||||
|
raise exception.InvalidInput(
|
||||||
|
reason=_('Required configuration not found'))
|
||||||
|
|
||||||
|
config.append_config_values(driver.share_opts)
|
||||||
|
config.append_config_values(cluster_mode.NETAPP_NAS_OPTS)
|
||||||
|
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
||||||
|
|
||||||
|
return NetAppDriver.create_driver(config.netapp_storage_family,
|
||||||
|
config.driver_handles_share_servers,
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_driver(storage_family, driver_handles_share_servers, *args,
|
||||||
|
**kwargs):
|
||||||
|
""""Creates an appropriate driver based on family and mode."""
|
||||||
|
|
||||||
|
storage_family = storage_family.lower()
|
||||||
|
|
||||||
|
# determine driver mode
|
||||||
|
if driver_handles_share_servers is None:
|
||||||
|
driver_mode = NETAPP_UNIFIED_DRIVER_DEFAULT_MODE.get(
|
||||||
|
storage_family)
|
||||||
|
|
||||||
|
if driver_mode:
|
||||||
|
LOG.debug('Default driver mode %s selected.' % driver_mode)
|
||||||
|
else:
|
||||||
|
raise exception.InvalidInput(
|
||||||
|
reason=_('Driver mode was not specified and a default '
|
||||||
|
'value could not be determined from the '
|
||||||
|
'specified storage family'))
|
||||||
|
elif driver_handles_share_servers:
|
||||||
|
driver_mode = 'multi_svm'
|
||||||
|
else:
|
||||||
|
driver_mode = 'single_svm'
|
||||||
|
|
||||||
|
fmt = {'storage_family': storage_family,
|
||||||
|
'driver_mode': driver_mode}
|
||||||
|
LOG.info(_LI('Requested unified config: %(storage_family)s and '
|
||||||
|
'%(driver_mode)s.') % fmt)
|
||||||
|
|
||||||
|
family_meta = NETAPP_UNIFIED_DRIVER_REGISTRY.get(storage_family)
|
||||||
|
if family_meta is None:
|
||||||
|
raise exception.InvalidInput(
|
||||||
|
reason=_('Storage family %s is not supported')
|
||||||
|
% storage_family)
|
||||||
|
|
||||||
|
driver_loc = family_meta.get(driver_mode)
|
||||||
|
if driver_loc is None:
|
||||||
|
raise exception.InvalidInput(
|
||||||
|
reason=_('Driver mode %(driver_mode)s is not supported '
|
||||||
|
'for storage family %(storage_family)s') % fmt)
|
||||||
|
|
||||||
|
kwargs = kwargs or {}
|
||||||
|
kwargs['netapp_mode'] = 'proxy'
|
||||||
|
driver = importutils.import_object(driver_loc, *args, **kwargs)
|
||||||
|
LOG.info(_LI('NetApp driver of family %(storage_family)s and mode '
|
||||||
|
'%(driver_mode)s loaded.') % fmt)
|
||||||
|
return driver
|
|
@ -25,7 +25,8 @@ import socket
|
||||||
from oslo_concurrency import processutils as putils
|
from oslo_concurrency import processutils as putils
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
from manila.i18n import _LI, _LW
|
from manila import exception
|
||||||
|
from manila.i18n import _, _LI, _LW
|
||||||
from manila.openstack.common import log as logging
|
from manila.openstack.common import log as logging
|
||||||
from manila.share.drivers.netapp import api as na_api
|
from manila.share.drivers.netapp import api as na_api
|
||||||
from manila import version
|
from manila import version
|
||||||
|
@ -34,6 +35,14 @@ from manila import version
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def check_flags(required_flags, configuration):
|
||||||
|
"""Ensure that the flags we care about are set."""
|
||||||
|
for flag in required_flags:
|
||||||
|
if not getattr(configuration, flag, None):
|
||||||
|
msg = _('Configuration value %s is not set.') % flag
|
||||||
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
|
|
||||||
def provide_ems(requester, server, netapp_backend, app_version,
|
def provide_ems(requester, server, netapp_backend, app_version,
|
||||||
server_type="cluster"):
|
server_type="cluster"):
|
||||||
"""Provide ems with volume stats for the requester.
|
"""Provide ems with volume stats for the requester.
|
||||||
|
|
|
@ -52,6 +52,10 @@ share_manager_opts = [
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
CONF.register_opts(share_manager_opts)
|
CONF.register_opts(share_manager_opts)
|
||||||
|
|
||||||
|
MAPPING = {
|
||||||
|
'manila.share.drivers.netapp.cluster_mode.NetAppClusteredShareDriver':
|
||||||
|
'manila.share.drivers.netapp.common.NetAppDriver', }
|
||||||
|
|
||||||
QUOTAS = quota.QUOTAS
|
QUOTAS = quota.QUOTAS
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,6 +72,13 @@ class ShareManager(manager.SchedulerDependentManager):
|
||||||
|
|
||||||
if not share_driver:
|
if not share_driver:
|
||||||
share_driver = self.configuration.share_driver
|
share_driver = self.configuration.share_driver
|
||||||
|
if share_driver in MAPPING:
|
||||||
|
msg_args = {'old': share_driver, 'new': MAPPING[share_driver]}
|
||||||
|
LOG.warning(_LW("Driver path %(old)s is deprecated, update your "
|
||||||
|
"configuration to the new path %(new)s"),
|
||||||
|
msg_args)
|
||||||
|
share_driver = MAPPING[share_driver]
|
||||||
|
|
||||||
self.driver = importutils.import_object(
|
self.driver = importutils.import_object(
|
||||||
share_driver, self.db, configuration=self.configuration)
|
share_driver, self.db, configuration=self.configuration)
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,19 @@ class TestCase(base_test.BaseTestCase):
|
||||||
self._services.append(svc)
|
self._services.append(svc)
|
||||||
return svc
|
return svc
|
||||||
|
|
||||||
|
def mock_object(self, obj, attr_name, new_attr=None, **kwargs):
|
||||||
|
"""Use python mock to mock an object attribute
|
||||||
|
|
||||||
|
Mocks the specified objects attribute with the given value.
|
||||||
|
Automatically performs 'addCleanup' for the mock.
|
||||||
|
"""
|
||||||
|
if not new_attr:
|
||||||
|
new_attr = mock.Mock()
|
||||||
|
patcher = mock.patch.object(obj, attr_name, new_attr, **kwargs)
|
||||||
|
patcher.start()
|
||||||
|
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):
|
||||||
"""Assert two dicts are equivalent.
|
"""Assert two dicts are equivalent.
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Copyright (c) 2015 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.
|
||||||
|
|
||||||
|
|
||||||
|
from manila.share import configuration as conf
|
||||||
|
from manila.share import driver as manila_opts
|
||||||
|
from manila.share.drivers.netapp import cluster_mode
|
||||||
|
|
||||||
|
|
||||||
|
def create_configuration():
|
||||||
|
config = conf.Configuration(None)
|
||||||
|
config.append_config_values(manila_opts.share_opts)
|
||||||
|
config.append_config_values(cluster_mode.NETAPP_NAS_OPTS)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def create_configuration_cmode():
|
||||||
|
config = create_configuration()
|
||||||
|
return config
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Copyright (c) 2015 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 manila import exception
|
||||||
|
from manila.share.drivers.netapp import cluster_mode
|
||||||
|
from manila.share.drivers.netapp import common as na_common
|
||||||
|
from manila import test
|
||||||
|
from manila.tests.share.drivers.netapp import fakes as na_fakes
|
||||||
|
|
||||||
|
|
||||||
|
class NetAppDriverFactoryTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(NetAppDriverFactoryTestCase, self).setUp()
|
||||||
|
self.mock_object(na_common, 'LOG')
|
||||||
|
|
||||||
|
def test_new(self):
|
||||||
|
|
||||||
|
mock_create_driver = self.mock_object(na_common.NetAppDriver,
|
||||||
|
'create_driver')
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.netapp_storage_family = 'fake_family'
|
||||||
|
config.driver_handles_share_servers = True
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
na_common.NetAppDriver(**kwargs)
|
||||||
|
|
||||||
|
mock_create_driver.assert_called_with('fake_family', True,
|
||||||
|
*(), **kwargs)
|
||||||
|
|
||||||
|
def test_new_missing_config(self):
|
||||||
|
|
||||||
|
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_common.NetAppDriver, 'create_driver')
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.driver_handles_share_servers = True
|
||||||
|
config.netapp_storage_family = None
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def test_new_missing_mode(self):
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.driver_handles_share_servers = 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__
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.local_conf.set_override('driver_handles_share_servers', True)
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
|
||||||
|
registry = na_common.NETAPP_UNIFIED_DRIVER_REGISTRY
|
||||||
|
mock_db = mock.Mock()
|
||||||
|
|
||||||
|
for family in six.iterkeys(registry):
|
||||||
|
for mode, full_class_name in six.iteritems(registry[family]):
|
||||||
|
driver = na_common.NetAppDriver.create_driver(
|
||||||
|
family, mode, mock_db, **kwargs)
|
||||||
|
self.assertEqual(full_class_name, get_full_class_name(driver))
|
||||||
|
|
||||||
|
def test_create_driver_case_insensitive(self):
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.local_conf.set_override('driver_handles_share_servers', True)
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
|
||||||
|
mock_db = mock.Mock()
|
||||||
|
|
||||||
|
driver = na_common.NetAppDriver.create_driver('ONTAP_CLUSTER',
|
||||||
|
True,
|
||||||
|
mock_db,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
self.assertIsInstance(driver,
|
||||||
|
cluster_mode.NetAppClusteredShareDriver)
|
||||||
|
|
||||||
|
def test_create_driver_invalid_family(self):
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration()}
|
||||||
|
mock_db = mock.Mock()
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver.create_driver,
|
||||||
|
'fake_family', 'iscsi', mock_db, **kwargs)
|
||||||
|
|
||||||
|
def test_create_driver_missing_mode_good_default(self):
|
||||||
|
|
||||||
|
config = na_fakes.create_configuration()
|
||||||
|
config.local_conf.set_override('driver_handles_share_servers', True)
|
||||||
|
|
||||||
|
kwargs = {'configuration': config}
|
||||||
|
mock_db = mock.Mock()
|
||||||
|
|
||||||
|
driver = na_common.NetAppDriver.create_driver('ONTAP_CLUSTER',
|
||||||
|
None,
|
||||||
|
mock_db,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
self.assertIsInstance(driver,
|
||||||
|
cluster_mode.NetAppClusteredShareDriver)
|
||||||
|
|
||||||
|
def test_create_driver_missing_mode_no_default(self):
|
||||||
|
|
||||||
|
kwargs = {'configuration': na_fakes.create_configuration()}
|
||||||
|
mock_db = mock.Mock()
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidInput,
|
||||||
|
na_common.NetAppDriver.create_driver,
|
||||||
|
'fake_family', None, mock_db, **kwargs)
|
Loading…
Reference in New Issue