glusterfs/layout: add layout base classes

Added:

class GlusterfsShareDriver(driver.ShareDrive)

@six.add_metaclass(abc.ABCMeta)
class GlusterfsShareLayoutBase(object)

Partially implements bp modular-glusterfs-share-layouts

Change-Id: I32d028afd736c5e93fc64c83dc0ab345a6335438
This commit is contained in:
Csaba Henk 2015-08-20 16:43:55 +02:00 committed by Ben Swartzlander
parent 15a9d4c5a6
commit f697cc0df4
3 changed files with 522 additions and 0 deletions

View File

@ -54,6 +54,7 @@ import manila.share.drivers.emc.driver
import manila.share.drivers.emc.plugins.isilon.isilon
import manila.share.drivers.generic
import manila.share.drivers.glusterfs
import manila.share.drivers.glusterfs.layout
import manila.share.drivers.glusterfs_native
import manila.share.drivers.hdfs.hdfs_native
import manila.share.drivers.hds.sop
@ -114,6 +115,7 @@ _global_opt_lists = [
manila.share.drivers.generic.share_opts,
manila.share.drivers.glusterfs.GlusterfsManilaShare_opts,
manila.share.drivers.glusterfs_native.glusterfs_native_manila_share_opts,
manila.share.drivers.glusterfs.layout.glusterfs_share_layout_opts,
manila.share.drivers.hdfs.hdfs_native.hdfs_native_share_opts,
manila.share.drivers.hds.sop.hdssop_share_opts,
manila.share.drivers.hitachi.hds_hnas.hds_hnas_opts,

View File

@ -0,0 +1,231 @@
# Copyright (c) 2015 Red Hat, Inc.
# 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.
"""GlusterFS share layouts.
A share layout encapsulates a particular way of mapping GlusterFS entities
to a share and utilizing them to back the share.
"""
import abc
import errno
from oslo_config import cfg
from oslo_utils import importutils
import six
from manila import exception
from manila.i18n import _
from manila.share import driver
glusterfs_share_layout_opts = [
cfg.StrOpt(
'glusterfs_share_layout',
help="Specifies GlusterFS share layout, that is, "
"the method of associating backing GlusterFS "
"resources to shares."),
]
CONF = cfg.CONF
CONF.register_opts(glusterfs_share_layout_opts)
class GlusterfsShareDriverBase(driver.ShareDriver):
LAYOUT_PREFIX = 'manila.share.drivers.glusterfs'
supported_layouts = ()
supported_protocols = ()
GLUSTERFS_VERSION_MIN = (0, 0)
def __init__(self, *args, **kwargs):
super(GlusterfsShareDriverBase, self).__init__(*args, **kwargs)
self.configuration.append_config_values(
glusterfs_share_layout_opts)
layout_name = self.configuration.glusterfs_share_layout
if not layout_name:
layout_name = self.supported_layouts[0]
if layout_name not in self.supported_layouts:
raise exception.GlusterfsException(
_('driver %(driver)s does not support %(layout)s layout') %
{'driver': type(self).__name__, 'layout': layout_name})
self.layout = importutils.import_object(
'.'.join((self.LAYOUT_PREFIX, layout_name)),
self, configuration=self.configuration)
# we determine snapshot support in our own scope, as
# 1) the calculation based on parent method
# redefinition does not work for us, as actual
# glusterfs driver classes are subclassed from
# *this* class, not from driver.ShareDriver
# and they don't need to redefine snapshot
# methods for themselves;
# 2) snapshot support depends on choice of layout.
self._snapshots_are_supported = getattr(self.layout,
'_snapshots_are_supported',
False)
def _setup_via_manager(self, gluster_mgr, gluster_mgr_parent=None):
"""Callback for layout's `create_share` and `create_share_from_snapshot`
:param gluster_mgr: GlusterManager instance
representing the GlusterFS resource that backs
the share created in `create_share` or
`create_share_from_snapshot`.
:param gluster_mgr_parent: GlusterManager instance
representing the GlusterFS resource
that backs the share the snapshot of which
was used in `create_share_from_snapshot`.
"""
def allow_access(self, context, share, access, share_server=None):
gluster_mgr = self.layout._share_manager(share)
return self._allow_access_via_manager(gluster_mgr, context, share,
access, share_server)
def deny_access(self, context, share, access, share_server=None):
gluster_mgr = self.layout._share_manager(share)
return self._deny_access_via_manager(gluster_mgr, context, share,
access, share_server)
def _allow_access_via_manager(self, gluster_mgr, context, share, access,
share_server):
raise NotImplementedError()
def _deny_access_via_manager(self, gluster_mgr, context, share, access,
share_server):
raise NotImplementedError()
def do_setup(self, *a, **kw):
return self.layout.do_setup(*a, **kw)
@classmethod
def _check_proto(cls, share):
proto = share['share_proto'].upper()
if proto not in cls.supported_protocols:
msg = _("Share protocol %s is not supported.") % proto
raise exception.ShareBackendException(msg=msg)
def create_share(self, context, share, *a, **kw):
self._check_proto(share)
return self.layout.create_share(context, share, *a, **kw)
def create_share_from_snapshot(self, context, share, *a, **kw):
self._check_proto(share)
return self.layout.create_share_from_snapshot(context, share, *a, **kw)
def create_snapshot(self, *a, **kw):
return self.layout.create_snapshot(*a, **kw)
def delete_share(self, *a, **kw):
return self.layout.delete_share(*a, **kw)
def delete_snapshot(self, *a, **kw):
return self.layout.delete_snapshot(*a, **kw)
def ensure_share(self, *a, **kw):
return self.layout.ensure_share(*a, **kw)
def manage_existing(self, *a, **kw):
return self.layout.manage_existing(*a, **kw)
def unmanage(self, *a, **kw):
return self.layout.unmanage(*a, **kw)
def extend_share(self, *a, **kw):
return self.layout.extend_share(*a, **kw)
def shrink_share(self, *a, **kw):
return self.layout.shrink_share(*a, **kw)
def _update_share_stats(self, data={}):
try:
data.update(self.layout._update_share_stats())
except NotImplementedError:
pass
super(GlusterfsShareDriverBase, self)._update_share_stats(data)
@six.add_metaclass(abc.ABCMeta)
class GlusterfsShareLayoutBase(object):
"""Base class for share layouts."""
def __init__(self, driver, *args, **kwargs):
self.driver = driver
self.configuration = kwargs.get('configuration')
def _check_mount_glusterfs(self):
"""Checks if mount.glusterfs(8) is available."""
try:
self.driver._execute('mount.glusterfs', check_exit_code=False)
except OSError as exc:
if exc.errno == errno.ENOENT:
raise exception.GlusterfsException(
_('mount.glusterfs is not installed.'))
else:
raise
@abc.abstractmethod
def _share_manager(self, share):
"""Return GlusterManager object representing share's backend."""
@abc.abstractmethod
def do_setup(self, context):
"""Any initialization the share driver does while starting."""
@abc.abstractmethod
def create_share(self, context, share, share_server=None):
"""Is called to create share."""
@abc.abstractmethod
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Is called to create share from snapshot."""
@abc.abstractmethod
def create_snapshot(self, context, snapshot, share_server=None):
"""Is called to create snapshot."""
@abc.abstractmethod
def delete_share(self, context, share, share_server=None):
"""Is called to remove share."""
@abc.abstractmethod
def delete_snapshot(self, context, snapshot, share_server=None):
"""Is called to remove snapshot."""
@abc.abstractmethod
def ensure_share(self, context, share, share_server=None):
"""Invoked to ensure that share is exported."""
@abc.abstractmethod
def manage_existing(self, share, driver_options):
"""Brings an existing share under Manila management."""
@abc.abstractmethod
def unmanage(self, share):
"""Removes the specified share from Manila management."""
@abc.abstractmethod
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share."""
@abc.abstractmethod
def shrink_share(self, share, new_size, share_server=None):
"""Shrinks size of existing share."""
def _update_share_stats(self):
raise NotImplementedError()

View File

@ -0,0 +1,289 @@
# Copyright (c) 2015 Red Hat, Inc.
# 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 errno
import os
import ddt
import mock
from oslo_config import cfg
from oslo_utils import importutils
from manila import exception
from manila.share import configuration as config
from manila.share import driver
from manila.share.drivers.glusterfs import layout
from manila import test
from manila.tests import fake_utils
CONF = cfg.CONF
fake_local_share_path = '/mnt/nfs/testvol/fakename'
fake_path_to_private_key = '/fakepath/to/privatekey'
fake_remote_server_password = 'fakepassword'
class GlusterfsFakeShareDriver(layout.GlusterfsShareDriverBase):
supported_layouts = ('layout_fake.FakeLayout',
'layout_something.SomeLayout')
supported_protocols = ('NFS,')
@ddt.ddt
class GlusterfsShareDriverBaseTestCase(test.TestCase):
"""Tests GlusterfsShareDriverBase."""
def setUp(self):
super(GlusterfsShareDriverBaseTestCase, self).setUp()
CONF.set_default('driver_handles_share_servers', False)
fake_conf, __ = self._setup()
self._driver = GlusterfsFakeShareDriver(False, configuration=fake_conf)
self.fake_share = mock.Mock()
self.fake_context = mock.Mock()
self.fake_access = mock.Mock()
def _setup(self):
fake_conf = config.Configuration(None)
fake_layout = mock.Mock()
self.mock_object(importutils, "import_object",
mock.Mock(return_value=fake_layout))
return fake_conf, fake_layout
def test_init(self):
self.assertRaises(IndexError, layout.GlusterfsShareDriverBase, False,
configuration=config.Configuration(None))
@ddt.data({'has_snap': None, 'layout_name': None},
{'has_snap': False, 'layout_name': 'layout_fake.FakeLayout'},
{'has_snap': True, 'layout_name': 'layout_something.SomeLayout'})
@ddt.unpack
def test_init_subclass(self, has_snap, layout_name):
conf, _layout = self._setup()
if layout_name is not None:
conf.glusterfs_share_layout = layout_name
if has_snap is None:
del(_layout._snapshots_are_supported)
else:
_layout._snapshots_are_supported = has_snap
_driver = GlusterfsFakeShareDriver(False, configuration=conf)
snap_result = {None: False}.get(has_snap, has_snap)
layout_result = {None: 'layout_fake.FakeLayout'}.get(layout_name,
layout_name)
importutils.import_object.assert_called_once_with(
'manila.share.drivers.glusterfs.%s' % layout_result,
_driver, configuration=conf)
self.assertEqual(_layout, _driver.layout)
self.assertEqual(snap_result, _driver.snapshots_are_supported)
def test_init_nosupp_layout(self):
conf = config.Configuration(None)
conf.glusterfs_share_layout = 'nonsense_layout'
self.assertRaises(exception.GlusterfsException,
GlusterfsFakeShareDriver, False, configuration=conf)
def test_setup_via_manager(self):
self.assertIsNone(self._driver._setup_via_manager(mock.Mock()))
@ddt.data('allow', 'deny')
def test_allow_deny_access(self, op):
conf, _layout = self._setup()
gmgr = mock.Mock()
self.mock_object(_layout, '_share_manager',
mock.Mock(return_value=gmgr))
_driver = GlusterfsFakeShareDriver(False, configuration=conf)
self.mock_object(_driver, "_%s_access_via_manager" % op, mock.Mock())
getattr(_driver, "%s_access" % op)(self.fake_context, self.fake_share,
self.fake_access)
_layout._share_manager.assert_called_once_with(self.fake_share)
getattr(_driver,
"_%s_access_via_manager" % op).assert_called_once_with(
gmgr, self.fake_context, self.fake_share, self.fake_access, None)
@ddt.data('allow', 'deny')
def test_allow_deny_access_via_manager(self, op):
self.assertRaises(NotImplementedError,
getattr(self._driver,
"_%s_access_via_manager" % op),
mock.Mock(), self.fake_context, self.fake_share,
self.fake_access, None)
@ddt.data('NFS', 'PROTATO')
def test_check_proto_baseclass(self, proto):
self.assertRaises(exception.ShareBackendException,
layout.GlusterfsShareDriverBase._check_proto,
{'share_proto': proto})
def test_check_proto(self):
GlusterfsFakeShareDriver._check_proto({'share_proto': 'NFS'})
def test_check_proto_notsupported(self):
self.assertRaises(exception.ShareBackendException,
GlusterfsFakeShareDriver._check_proto,
{'share_proto': 'PROTATO'})
@ddt.data('', '_from_snapshot')
def test_create_share(self, variant):
conf, _layout = self._setup()
_driver = GlusterfsFakeShareDriver(False, configuration=conf)
self.mock_object(_driver, '_check_proto', mock.Mock())
getattr(_driver, 'create_share%s' % variant)(self.fake_context,
self.fake_share)
_driver._check_proto.assert_called_once_with(self.fake_share)
getattr(_layout,
'create_share%s' % variant).assert_called_once_with(
self.fake_context, self.fake_share)
@ddt.data(True, False)
def test_update_share_stats(self, internal_exception):
data = mock.Mock()
conf, _layout = self._setup()
def raise_exception(*args, **kwargs):
raise NotImplementedError
layoutstats = mock.Mock()
mock_kw = ({'side_effect': raise_exception} if internal_exception
else {'return_value': layoutstats})
self.mock_object(_layout, '_update_share_stats', mock.Mock(**mock_kw))
self.mock_object(driver.ShareDriver, '_update_share_stats',
mock.Mock())
_driver = GlusterfsFakeShareDriver(False, configuration=conf)
_driver._update_share_stats(data)
if internal_exception:
self.assertFalse(data.update.called)
else:
data.update.assert_called_once_with(layoutstats)
driver.ShareDriver._update_share_stats.assert_called_once_with(
data)
@ddt.data('do_setup', 'create_snapshot', 'delete_share', 'delete_snapshot',
'ensure_share', 'manage_existing', 'unmanage', 'extend_share',
'shrink_share')
def test_delegated_methods(self, method):
conf, _layout = self._setup()
_driver = GlusterfsFakeShareDriver(False, configuration=conf)
fake_args = (mock.Mock(), mock.Mock(), mock.Mock())
getattr(_driver, method)(*fake_args)
getattr(_layout, method).assert_called_once_with(*fake_args)
@ddt.ddt
class GlusterfsShareLayoutBaseTestCase(test.TestCase):
"""Tests GlusterfsShareLayoutBaseTestCase."""
def setUp(self):
super(GlusterfsShareLayoutBaseTestCase, self).setUp()
fake_utils.stub_out_utils_execute(self)
self._execute = fake_utils.fake_execute
self.addCleanup(fake_utils.fake_execute_set_repliers, [])
self.addCleanup(fake_utils.fake_execute_clear_log)
self.fake_driver = mock.Mock()
self.mock_object(self.fake_driver, '_execute',
self._execute)
class FakeLayout(layout.GlusterfsShareLayoutBase):
def _share_manager(self, share):
"""Return GlusterManager object representing share's backend."""
def do_setup(self, context):
"""Any initialization the share driver does while starting."""
def create_share(self, context, share, share_server=None):
"""Is called to create share."""
def create_share_from_snapshot(self, context, share, snapshot,
share_server=None):
"""Is called to create share from snapshot."""
def create_snapshot(self, context, snapshot, share_server=None):
"""Is called to create snapshot."""
def delete_share(self, context, share, share_server=None):
"""Is called to remove share."""
def delete_snapshot(self, context, snapshot, share_server=None):
"""Is called to remove snapshot."""
def ensure_share(self, context, share, share_server=None):
"""Invoked to ensure that share is exported."""
def manage_existing(self, share, driver_options):
"""Brings an existing share under Manila management."""
def unmanage(self, share):
"""Removes the specified share from Manila management."""
def extend_share(self, share, new_size, share_server=None):
"""Extends size of existing share."""
def shrink_share(self, share, new_size, share_server=None):
"""Shrinks size of existing share."""
def test_init_invalid(self):
self.assertRaises(TypeError, layout.GlusterfsShareLayoutBase,
mock.Mock())
def test_subclass(self):
fake_conf = mock.Mock()
_layout = self.FakeLayout(self.fake_driver, configuration=fake_conf)
self.assertEqual(fake_conf, _layout.configuration)
self.assertRaises(NotImplementedError, _layout._update_share_stats)
def test_check_mount_glusterfs(self):
fake_conf = mock.Mock()
_driver = mock.Mock()
_driver._execute = mock.Mock()
_layout = self.FakeLayout(_driver, configuration=fake_conf)
_layout._check_mount_glusterfs()
_driver._execute.assert_called_once_with(
'mount.glusterfs',
check_exit_code=False)
@ddt.data({'_errno': errno.ENOENT,
'_exception': exception.GlusterfsException},
{'_errno': errno.EACCES, '_exception': OSError})
@ddt.unpack
def test_check_mount_glusterfs_not_installed(self, _errno, _exception):
fake_conf = mock.Mock()
_layout = self.FakeLayout(self.fake_driver, configuration=fake_conf)
def exec_runner(*ignore_args, **ignore_kwargs):
raise OSError(_errno, os.strerror(_errno))
expected_exec = ['mount.glusterfs']
fake_utils.fake_execute_set_repliers([(expected_exec[0], exec_runner)])
self.assertRaises(_exception, _layout._check_mount_glusterfs)