Add Ceph Native driver

This driver enables use of the Ceph filesystem for
Manila shares.  Clients require a native CephFS
client to access the share.  The interface
to Ceph is CephFSVolumeClient, included in
the 'Jewel' Ceph release and later.

APIImpact
The API microversion is bumped to 2.13 to add a
new access type, cephx, supported by the driver.

Co-Authored-By: Ramana Raja <rraja@redhat.com>

Implements: blueprint cephfs-driver
Change-Id: I33801215f64eacb9dade4d92f11f659173abb7f5
This commit is contained in:
John Spray 2015-11-18 13:15:32 +00:00 committed by John Spray
parent 86c05e9bf7
commit 9ff0d1312e
22 changed files with 1118 additions and 52 deletions

View File

@ -0,0 +1,136 @@
CephFS Native driver
====================
The CephFS Native driver enables manila to export shared filesystems to guests
using the Ceph network protocol. Guests require a Ceph client in order to
mount the filesystem.
Access is controlled via Ceph's cephx authentication system. Each share has
a distinct authentication key that must be passed to clients for them to use
it.
To learn more about configuring Ceph clients to access the shares created
using this driver, please see the Ceph documentation(
http://docs.ceph.com/docs/master/cephfs/). If you choose to use the kernel
client rather than the FUSE client, the share size limits set in Manila
may not be obeyed.
Prerequisities
--------------
- A Ceph cluster with a filesystem configured (
http://docs.ceph.com/docs/master/cephfs/createfs/)
- Network connectivity between your Ceph cluster's public network and the
server running the :term:`manila-share` service.
- Network connectivity between your Ceph cluster's public network and guests
.. important:: A manila share backed onto CephFS is only as good as the
underlying filesystem. Take care when configuring your Ceph
cluster, and consult the latest guidance on the use of
CephFS in the Ceph documentation (
http://docs.ceph.com/docs/master/cephfs/)
Authorize the driver to communicate with Ceph
---------------------------------------------
Run the following command to create a Ceph identity for manila to use:
.. code-block:: console
ceph auth get-or-create client.manila mon 'allow r; allow command "auth del" with entity prefix client.manila.; allow command "auth caps" with entity prefix client.manila.; allow command "auth get" with entity prefix client.manila., allow command "auth get-or-create" with entity prefix client.manila.' mds 'allow *' osd 'allow rw' > keyring.manila
keyring.manila, along with your ceph.conf file, will then need to be placed
on the server where the :term:`manila-share` service runs, and the paths to these
configured in your manila.conf.
Enable snapshots in Ceph if you want to use them in manila:
.. code-block:: console
ceph mds set allow_new_snaps true --yes-i-really-mean-it
Configure CephFS backend in manila.conf
---------------------------------------
Add CephFS to ``enabled_share_protocols`` (enforced at manila api layer). In
this example we leave NFS and CIFS enabled, although you can remove these
if you will only use CephFS:
.. code-block:: ini
enabled_share_protocols = NFS,CIFS,CEPHFS
Create a section like this to define a CephFS backend:
.. code-block:: ini
[cephfs1]
driver_handles_share_servers = False
share_backend_name = CEPHFS1
share_driver = manila.share.drivers.cephfs.cephfs_native.CephFSNativeDriver
cephfs_conf_path = /etc/ceph/ceph.conf
cephfs_auth_id = manila
Then edit ``enabled_share_backends`` to point to it, using the same
name that you used for the backend section. In this example we are
also including another backend ("generic1"), you would include
whatever other backends you have configured.
.. code-block:: ini
enabled_share_backends = generic1, cephfs1
Creating shares
---------------
The default share type may have driver_handles_share_servers set to True.
Configure a share type suitable for cephfs:
.. code-block:: console
manila type-create cephfstype false
Then create yourself a share:
.. code-block:: console
manila create --share-type cephfstype --name cephshare1 cephfs 1
Mounting a client with FUSE
---------------------------
Using the key from your export location, and the share ID, create a keyring
file like:
.. code-block:: ini
[client.share-4c55ad20-9c55-4a5e-9233-8ac64566b98c]
key = AQA8+ANW/4ZWNRAAOtWJMFPEihBA1unFImJczA==
Using the mon IP addresses from your export location, create a ceph.conf file
like:
.. code-block:: ini
[client]
client quota = true
[mon.a]
mon addr = 192.168.1.7:6789
[mon.b]
mon addr = 192.168.1.8:6789
[mon.c]
mon addr = 192.168.1.9:6789
Finally, mount the filesystem, substituting the filenames of the keyring and
configuration files you just created:
.. code-block:: console
ceph-fuse --id=share-4c55ad20-9c55-4a5e-9233-8ac64566b98c -c ./client.conf --keyring=./client.keyring --client-mountpoint=/volumes/share-4c55ad20-9c55-4a5e-9233-8ac64566b98c ~/mnt

View File

@ -106,6 +106,7 @@ Share backends
generic_driver
glusterfs_driver
glusterfs_native_driver
cephfs_native_driver
gpfs_driver
huawei_nas_driver
hdfs_native_driver

View File

@ -65,6 +65,8 @@ Mapping of share drivers and share features support
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| Oracle ZFSSA | DHSS = False (K) | \- | M | M | K | K | \- |
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
| CephFS Native | DHSS = False (M) | \- | M | M | M | \- | \- |
+----------------------------------------+-----------------------------+-----------------------+--------------+--------------+------------------------+----------------------------+--------------------------+
.. note::
@ -73,43 +75,45 @@ Mapping of share drivers and share features support
Mapping of share drivers and share access rules support
-------------------------------------------------------
+----------------------------------------+--------------------------------------------+--------------------------------------------+
| | Read & Write | Read Only |
+ Driver name +--------------+----------------+------------+--------------+----------------+------------+
| | IP | USER | Cert | IP | USER | Cert |
+========================================+==============+================+============+==============+================+============+
| ZFSonLinux | NFS (M) | \- | \- | NFS (M) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | NFS (K) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | NFS (K) | CIFS (M) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| EMC VNX | NFS (J) | CIFS (J) | \- | NFS (L) | CIFS (L) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| EMC Isilon | NFS,CIFS (K) | CIFS (M) | \- | NFS (M) | CIFS (M) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| HDFS | \- | HDFS(K) | \- | \- | HDFS(K) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Hitachi HNAS | NFS (L) | \- | \- | NFS (L) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Huawei | NFS (K) |NFS (M),CIFS (K)| \- | NFS (K) |NFS (M),CIFS (K)| \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| LVM | NFS (M) | CIFS (M) | \- | NFS (M) | CIFS (M) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Quobyte | NFS (K) | \- | \- | NFS (K) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Windows SMB | \- | CIFS (L) | \- | \- | CIFS (L) | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| IBM GPFS | NFS (K) | \- | \- | NFS (K) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+----------------+------------+
+----------------------------------------+-----------------------------------------------------------+---------------------------------------------------------+
| | Read & Write | Read Only |
+ Driver name +--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| | IP | USER | Cert | CephX | IP | USER | Cert | CephX |
+========================================+==============+================+============+==============+==============+================+============+============+
| ZFSonLinux | NFS (M) | \- | \- | \- | NFS (M) | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Generic (Cinder as back-end) | NFS,CIFS (J) | \- | \- | \- | NFS (K) | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| NetApp Clustered Data ONTAP | NFS (J) | CIFS (J) | \- | \- | NFS (K) | CIFS (M) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| EMC VNX | NFS (J) | CIFS (J) | \- | \- | NFS (L) | CIFS (L) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| EMC Isilon | NFS,CIFS (K) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Red Hat GlusterFS | NFS (J) | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Red Hat GlusterFS-Native | \- | \- | J | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| HDFS | \- | HDFS(K) | \- | \- | \- | HDFS(K) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Hitachi HNAS | NFS (L) | \- | \- | \- | NFS (L) | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| HPE 3PAR | NFS,CIFS (K) | CIFS (K) | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Huawei | NFS (K) |NFS (M),CIFS (K)| \- | \- | NFS (K) |NFS (M),CIFS (K)| \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| LVM | NFS (M) | CIFS (M) | \- | \- | NFS (M) | CIFS (M) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Quobyte | NFS (K) | \- | \- | \- | NFS (K) | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Windows SMB | \- | CIFS (L) | \- | \- | \- | CIFS (L) | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| IBM GPFS | NFS (K) | \- | \- | \- | NFS (K) | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| Oracle ZFSSA | NFS,CIFS(K) | \- | \- | \- | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
| CephFS Native | \- | \- | \- | CEPH(M) | \- | \- | \- | \- |
+----------------------------------------+--------------+----------------+------------+--------------+--------------+----------------+------------+------------+
Mapping of share drivers and security services support
------------------------------------------------------
@ -149,3 +153,6 @@ Mapping of share drivers and security services support
+----------------------------------------+------------------+-----------------+------------------+
| Oracle ZFSSA | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+
| CephFS Native | \- | \- | \- |
+----------------------------------------+------------------+-----------------+------------------+

View File

@ -59,13 +59,14 @@ REST_API_VERSION_HISTORY = """
instances.
* 2.11 - Share Replication support
* 2.12 - Manage/unmanage snapshot API.
* 2.13 - Add "cephx" auth type to allow_access
"""
# The minimum and maximum versions of the API supported
# The default api version request is defined to be the
# the minimum version of the API supported.
_MIN_API_VERSION = "2.0"
_MAX_API_VERSION = "2.12"
_MAX_API_VERSION = "2.13"
DEFAULT_API_VERSION = _MIN_API_VERSION

View File

@ -89,3 +89,7 @@ user documentation.
2.12
----
Share snapshot manage and unmanage API.
2.13
----
Add 'cephx' authentication type for the CephFS Native driver.

View File

@ -17,6 +17,7 @@
import ast
import re
import string
from oslo_log import log
from oslo_utils import strutils
@ -383,7 +384,27 @@ class ShareMixin(object):
except ValueError:
raise webob.exc.HTTPBadRequest(explanation=exc_str)
def _allow_access(self, req, id, body):
@staticmethod
def _validate_cephx_id(cephx_id):
if not cephx_id:
raise webob.exc.HTTPBadRequest(explanation=_(
'Ceph IDs may not be empty'))
# This restriction may be lifted in Ceph in the future:
# http://tracker.ceph.com/issues/14626
if not set(cephx_id) <= set(string.printable):
raise webob.exc.HTTPBadRequest(explanation=_(
'Ceph IDs must consist of ASCII printable characters'))
# Periods are technically permitted, but we restrict them here
# to avoid confusion where users are unsure whether they should
# include the "client." prefix: otherwise they could accidentally
# create "client.client.foobar".
if '.' in cephx_id:
raise webob.exc.HTTPBadRequest(explanation=_(
'Ceph IDs may not contain periods'))
def _allow_access(self, req, id, body, enable_ceph=False):
"""Add share access rule."""
context = req.environ['manila.context']
access_data = body.get('allow_access', body.get('os-allow_access'))
@ -397,9 +418,16 @@ class ShareMixin(object):
self._validate_username(access_to)
elif access_type == 'cert':
self._validate_common_name(access_to.strip())
elif access_type == "cephx" and enable_ceph:
self._validate_cephx_id(access_to)
else:
exc_str = _("Only 'ip','user',or'cert' access types "
"are supported.")
if enable_ceph:
exc_str = _("Only 'ip', 'user', 'cert' or 'cephx' access "
"types are supported.")
else:
exc_str = _("Only 'ip', 'user' or 'cert' access types "
"are supported.")
raise webob.exc.HTTPBadRequest(explanation=exc_str)
try:
access = self.share_api.allow_access(

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from manila.api.openstack import api_version_request as api_version
from manila.api.openstack import wsgi
from manila.api.v1 import share_manage
from manila.api.v1 import share_unmanage
@ -27,7 +28,6 @@ class ShareController(shares.ShareMixin,
wsgi.Controller,
wsgi.AdminActionsMixin):
"""The Shares API v2 controller for the OpenStack API."""
resource_name = 'share'
_view_builder_class = share_views.ViewBuilder
@ -86,7 +86,10 @@ class ShareController(shares.ShareMixin,
@wsgi.action('allow_access')
def allow_access(self, req, id, body):
"""Add share access rule."""
return self._allow_access(req, id, body)
if req.api_version_request < api_version.APIVersionRequest("2.13"):
return self._allow_access(req, id, body)
else:
return self._allow_access(req, id, body, enable_ceph=True)
@wsgi.Controller.api_version('2.0', '2.6')
@wsgi.action('os-deny_access')

View File

@ -55,7 +55,7 @@ TRANSITIONAL_STATUSES = (
)
SUPPORTED_SHARE_PROTOCOLS = (
'NFS', 'CIFS', 'GLUSTERFS', 'HDFS')
'NFS', 'CIFS', 'GLUSTERFS', 'HDFS', 'CEPHFS')
SECURITY_SERVICES_ALLOWED_TYPES = ['active_directory', 'ldap', 'kerberos']

View File

@ -50,6 +50,7 @@ import manila.scheduler.weighers.pool
import manila.service
import manila.share.api
import manila.share.driver
import manila.share.drivers.cephfs.cephfs_native
import manila.share.drivers.emc.driver
import manila.share.drivers.emc.plugins.isilon.isilon
import manila.share.drivers.generic
@ -113,6 +114,7 @@ _global_opt_lists = [
manila.share.driver.share_opts,
manila.share.driver.ssh_opts,
manila.share.drivers_private_data.private_data_opts,
manila.share.drivers.cephfs.cephfs_native.cephfs_native_opts,
manila.share.drivers.emc.driver.EMC_NAS_OPTS,
manila.share.drivers.generic.share_opts,
manila.share.drivers.glusterfs.common.glusterfs_common_opts,

View File

View File

@ -0,0 +1,319 @@
# Copyright (c) 2016 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.
from oslo_config import cfg
from oslo_log import log
from oslo_utils import units
from manila.common import constants
from manila import exception
from manila.i18n import _, _LI, _LW
from manila.share import driver
from manila.share import share_types
try:
import ceph_volume_client
ceph_module_found = True
except ImportError as e:
ceph_volume_client = None
ceph_module_found = False
CEPHX_ACCESS_TYPE = "cephx"
# The default Ceph administrative identity
CEPH_DEFAULT_AUTH_ID = "admin"
LOG = log.getLogger(__name__)
cephfs_native_opts = [
cfg.StrOpt('cephfs_conf_path',
default="",
help="Fully qualified path to the ceph.conf file."),
cfg.StrOpt('cephfs_cluster_name',
help="The name of the cluster in use, if it is not "
"the default ('ceph')."
),
cfg.StrOpt('cephfs_auth_id',
default="manila",
help="The name of the ceph auth identity to use."
),
cfg.BoolOpt('cephfs_enable_snapshots',
default=False,
help="Whether to enable snapshots in this driver."
),
]
CONF = cfg.CONF
CONF.register_opts(cephfs_native_opts)
class CephFSNativeDriver(driver.ShareDriver,):
"""Driver for the Ceph Filsystem.
This driver is 'native' in the sense that it exposes a CephFS filesystem
for use directly by guests, with no intermediate layer like NFS.
"""
supported_protocols = ('CEPHFS',)
def __init__(self, *args, **kwargs):
super(CephFSNativeDriver, self).__init__(False, *args, **kwargs)
self.backend_name = self.configuration.safe_get(
'share_backend_name') or 'CephFS-Native'
self._volume_client = None
self.configuration.append_config_values(cephfs_native_opts)
def _update_share_stats(self):
stats = self.volume_client.rados.get_cluster_stats()
total_capacity_gb = stats['kb'] * units.Mi
free_capacity_gb = stats['kb_avail'] * units.Mi
data = {
'consistency_group_support': 'pool',
'vendor_name': 'Ceph',
'driver_version': '1.0',
'share_backend_name': self.backend_name,
'storage_protocol': "CEPHFS",
'pools': [
{
'pool_name': 'cephfs',
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'qos': 'False',
'reserved_percentage': 0,
'dedupe': [False],
'compression': [False],
'thin_provisioning': [False]
}
],
'total_capacity_gb': total_capacity_gb,
'free_capacity_gb': free_capacity_gb,
'snapshot_support': self.configuration.safe_get(
'cephfs_enable_snapshots'),
}
super(CephFSNativeDriver, self)._update_share_stats(data)
def _to_bytes(self, gigs):
"""Convert a Manila size into bytes.
Manila uses gibibytes everywhere.
:param gigs: integer number of gibibytes.
:return: integer number of bytes.
"""
return gigs * units.Gi
@property
def volume_client(self):
if self._volume_client:
return self._volume_client
if not ceph_module_found:
raise exception.ManilaException(
_("Ceph client libraries not found.")
)
conf_path = self.configuration.safe_get('cephfs_conf_path')
cluster_name = self.configuration.safe_get('cephfs_cluster_name')
auth_id = self.configuration.safe_get('cephfs_auth_id')
self._volume_client = ceph_volume_client.CephFSVolumeClient(
auth_id, conf_path, cluster_name)
LOG.info(_LI("[%(be)s}] Ceph client found, connecting..."),
{"be": self.backend_name})
if auth_id != CEPH_DEFAULT_AUTH_ID:
# Evict any other manila sessions. Only do this if we're
# using a client ID that isn't the default admin ID, to avoid
# rudely disrupting anyone else.
premount_evict = auth_id
else:
premount_evict = None
try:
self._volume_client.connect(premount_evict=premount_evict)
except Exception:
self._volume_client = None
raise
else:
LOG.info(_LI("[%(be)s] Ceph client connection complete."),
{"be": self.backend_name})
return self._volume_client
def _share_path(self, share):
"""Get VolumePath from Share."""
return ceph_volume_client.VolumePath(
share['consistency_group_id'], share['id'])
def create_share(self, context, share, share_server=None):
"""Create a CephFS volume.
:param context: A RequestContext.
:param share: A Share.
:param share_server: Always None for CephFS native.
:return: The export locations dictionary.
"""
# `share` is a Share
LOG.debug("create_share {be} name={id} size={size} cg_id={cg}".format(
be=self.backend_name, id=share['id'], size=share['size'],
cg=share['consistency_group_id']))
extra_specs = share_types.get_extra_specs_from_share(share)
data_isolated = extra_specs.get("cephfs:data_isolated", False)
size = self._to_bytes(share['size'])
# Create the CephFS volume
volume = self.volume_client.create_volume(
self._share_path(share), size=size, data_isolated=data_isolated)
# To mount this you need to know the mon IPs and the path to the volume
mon_addrs = self.volume_client.get_mon_addrs()
export_location = "{addrs}:{path}".format(
addrs=",".join(mon_addrs),
path=volume['mount_path'])
LOG.info(_LI("Calculated export location for share %(id)s: %(loc)s"),
{"id": share['id'], "loc": export_location})
return {
'path': export_location,
'is_admin_only': False,
'metadata': {},
}
def _allow_access(self, context, share, access, share_server=None):
if access['access_type'] != CEPHX_ACCESS_TYPE:
raise exception.InvalidShareAccess(
reason=_("Only 'cephx' access type allowed."))
if access['access_level'] == constants.ACCESS_LEVEL_RO:
raise exception.InvalidShareAccessLevel(
level=constants.ACCESS_LEVEL_RO)
ceph_auth_id = access['access_to']
auth_result = self.volume_client.authorize(self._share_path(share),
ceph_auth_id)
return auth_result['auth_key']
def _deny_access(self, context, share, access, share_server=None):
if access['access_type'] != CEPHX_ACCESS_TYPE:
LOG.warning(_LW("Invalid access type '%(type)s', "
"ignoring in deny."),
{"type": access['access_type']})
return
self.volume_client.deauthorize(self._share_path(share),
access['access_to'])
self.volume_client.evict(access['access_to'])
def update_access(self, context, share, access_rules, add_rules=None,
delete_rules=None, share_server=None):
# The interface to Ceph just provides add/remove methods, since it
# was created at start of mitaka cycle when there was no requirement
# to be able to list access rules or set them en masse. Therefore
# we implement update_access as best we can. In future ceph's
# interface should be extended to enable a full implementation
# of update_access.
for rule in add_rules:
self._allow_access(context, share, rule)
for rule in delete_rules:
self._deny_access(context, share, rule)
# This is where we would list all permitted clients and remove
# those that are not in `access_rules` if the ceph interface
# enabled it.
if not (add_rules or delete_rules):
for rule in access_rules:
self._allow_access(context, share, rule)
def delete_share(self, context, share, share_server=None):
extra_specs = share_types.get_extra_specs_from_share(share)
data_isolated = extra_specs.get("cephfs:data_isolated", False)
self.volume_client.delete_volume(self._share_path(share),
data_isolated=data_isolated)
self.volume_client.purge_volume(self._share_path(share),
data_isolated=data_isolated)
def ensure_share(self, context, share, share_server=None):
# Creation is idempotent
return self.create_share(context, share, share_server)
def extend_share(self, share, new_size, share_server=None):
LOG.debug("extend_share {id} {size}".format(
id=share['id'], size=new_size))
self.volume_client.set_max_bytes(self._share_path(share),
self._to_bytes(new_size))
def shrink_share(self, share, new_size, share_server=None):
LOG.debug("shrink_share {id} {size}".format(
id=share['id'], size=new_size))
new_bytes = self._to_bytes(new_size)
used = self.volume_client.get_used_bytes(self._share_path(share))
if used > new_bytes:
# While in fact we can "shrink" our volumes to less than their
# used bytes (it's just a quota), raise error anyway to avoid
# confusing API consumers that might depend on typical shrink
# behaviour.
raise exception.ShareShrinkingPossibleDataLoss(
share_id=share['id'])
self.volume_client.set_max_bytes(self._share_path(share), new_bytes)
def create_snapshot(self, context, snapshot, share_server=None):
self.volume_client.create_snapshot_volume(
self._share_path(snapshot['share']), snapshot['name'])
def delete_snapshot(self, context, snapshot, share_server=None):
self.volume_client.destroy_snapshot_volume(
self._share_path(snapshot['share']), snapshot['name'])
def create_consistency_group(self, context, cg_dict, share_server=None):
self.volume_client.create_group(cg_dict['id'])
def delete_consistency_group(self, context, cg_dict, share_server=None):
self.volume_client.destroy_group(cg_dict['id'])
def delete_cgsnapshot(self, context, snap_dict, share_server=None):
self.volume_client.destroy_snapshot_group(
snap_dict['consistency_group_id'],
snap_dict['id'])
return None, []
def create_cgsnapshot(self, context, snap_dict, share_server=None):
self.volume_client.create_snapshot_group(
snap_dict['consistency_group_id'],
snap_dict['id'])
return None, []
def __del__(self):
if self._volume_client:
self._volume_client.disconnect()
self._volume_client = None

View File

@ -753,6 +753,20 @@ class ShareAPITest(test.TestCase):
common.remove_invalid_options(ctx, search_opts, allowed_opts)
self.assertEqual(expected_opts, search_opts)
def test_validate_cephx_id_invalid_with_period(self):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._validate_cephx_id,
"client.manila")
def test_validate_cephx_id_invalid_with_non_ascii(self):
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._validate_cephx_id,
u"bj\u00F6rn")
@ddt.data("alice", "alice_bob", "alice bob")
def test_validate_cephx_id_valid(self, test_id):
self.controller._validate_cephx_id(test_id)
def _fake_access_get(self, ctxt, access_id):
@ -816,6 +830,7 @@ class ShareActionsTest(test.TestCase):
{'access_type': 'cert', 'access_to': ''},
{'access_type': 'cert', 'access_to': ' '},
{'access_type': 'cert', 'access_to': 'x' * 65},
{'access_type': 'cephx', 'access_to': 'alice'}
)
def test_allow_access_error(self, access):
id = 'fake_share_id'

View File

@ -1017,10 +1017,11 @@ class ShareActionsTest(test.TestCase):
mock.Mock(return_value={'fake': 'fake'}))
id = 'fake_share_id'
body = {'os-allow_access': access}
body = {'allow_access': access}
expected = {'access': {'fake': 'fake'}}
req = fakes.HTTPRequest.blank('/v1/tenant1/shares/%s/action' % id)
res = self.controller._allow_access(req, id, body)
req = fakes.HTTPRequest.blank(
'/v2/tenant1/shares/%s/action' % id, version="2.7")
res = self.controller.allow_access(req, id, body)
self.assertEqual(expected, res)
@ddt.data(
@ -1039,10 +1040,41 @@ class ShareActionsTest(test.TestCase):
)
def test_allow_access_error(self, access):
id = 'fake_share_id'
body = {'os-allow_access': access}
req = fakes.HTTPRequest.blank('/v1/tenant1/shares/%s/action' % id)
body = {'allow_access': access}
req = fakes.HTTPRequest.blank('/v2/tenant1/shares/%s/action' % id,
version="2.7")
self.assertRaises(webob.exc.HTTPBadRequest,
self.controller._allow_access, req, id, body)
self.controller.allow_access, req, id, body)
@ddt.unpack
@ddt.data(
{'exc': None, 'access_to': 'alice', 'version': '2.13'},
{'exc': webob.exc.HTTPBadRequest, 'access_to': 'alice',
'version': '2.11'}
)
def test_allow_access_ceph(self, exc, access_to, version):
share_id = "fake_id"
self.mock_object(share_api.API,
'allow_access',
mock.Mock(return_value={'fake': 'fake'}))
req = fakes.HTTPRequest.blank(
'/v2/shares/%s/action' % share_id, version=version)
body = {'allow_access':
{
'access_type': 'cephx',
'access_to': access_to,
'access_level': 'rw'
}}
if exc:
self.assertRaises(exc, self.controller.allow_access, req, share_id,
body)
else:
expected = {'access': {'fake': 'fake'}}
res = self.controller.allow_access(req, id, body)
self.assertEqual(expected, res)
def test_deny_access(self):
def _stub_deny_access(*args, **kwargs):

View File

@ -16,6 +16,7 @@
import datetime
import uuid
from manila.db.sqlalchemy import models
from manila.tests.db import fakes as db_fakes
@ -27,17 +28,37 @@ def fake_share(**kwargs):
'share_proto': 'fake_proto',
'share_network_id': 'fake share network id',
'share_server_id': 'fake share server id',
'share_type_id': 'fake share type id',
'export_location': 'fake_location:/fake_share',
'project_id': 'fake_project_uuid',
'availability_zone': 'fake_az',
'snapshot_support': 'True',
'replication_type': None,
'is_busy': False,
'consistency_group_id': 'fakecgid',
}
share.update(kwargs)
return db_fakes.FakeModel(share)
def fake_share_instance(base_share=None, **kwargs):
if base_share is None:
share = fake_share()
else:
share = base_share
share_instance = {
'share_id': share['id'],
'id': "fakeinstanceid",
'status': "active",
}
for attr in models.ShareInstance._proxified_properties:
share_instance[attr] = getattr(share, attr, None)
return db_fakes.FakeModel(share_instance)
def fake_snapshot(**kwargs):
snapshot = {
'id': 'fakesnapshotid',

View File

@ -0,0 +1,374 @@
# Copyright (c) 2016 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 mock
from oslo_utils import units
from manila.common import constants
from manila import context
import manila.exception as exception
from manila.share import configuration
from manila.share.drivers.cephfs import cephfs_native
from manila.share import share_types
from manila import test
from manila.tests import fake_share
class MockVolumeClientModule(object):
"""Mocked up version of ceph's VolumeClient interface."""
class VolumePath(object):
"""Copy of VolumePath from CephFSVolumeClient."""
def __init__(self, group_id, volume_id):
self.group_id = group_id
self.volume_id = volume_id
def __eq__(self, other):
return (self.group_id == other.group_id
and self.volume_id == other.volume_id)
def __str__(self):
return "{0}/{1}".format(self.group_id, self.volume_id)
class CephFSVolumeClient(mock.Mock):
mock_used_bytes = 0
def __init__(self, *args, **kwargs):
mock.Mock.__init__(self, spec=[
"connect", "disconnect",
"create_snapshot_volume", "destroy_snapshot_volume",
"create_group", "destroy_group",
"delete_volume", "purge_volume",
"deauthorize", "evict", "set_max_bytes",
"destroy_snapshot_group", "create_snapshot_group",
"disconnect"
])
self.create_volume = mock.Mock(return_value={
"mount_path": "/foo/bar"
})
self.get_mon_addrs = mock.Mock(return_value=["1.2.3.4", "5.6.7.8"])
self.authorize = mock.Mock(return_value={"auth_key": "abc123"})
self.get_used_bytes = mock.Mock(return_value=self.mock_used_bytes)
self.rados = mock.Mock()
self.rados.get_cluster_stats = mock.Mock(return_value={
"kb": 1000,
"kb_avail": 500
})
class CephFSNativeDriverTestCase(test.TestCase):
"""Test the CephFS native driver.
This is a very simple driver that mainly
calls through to the CephFSVolumeClient interface, so the tests validate
that the Manila driver calls map to the appropriate CephFSVolumeClient
calls.
"""
def setUp(self):
super(CephFSNativeDriverTestCase, self).setUp()
self.fake_conf = configuration.Configuration(None)
self._context = context.get_admin_context()
self._share = fake_share.fake_share(share_proto='CEPHFS')
self.fake_conf.set_default('driver_handles_share_servers', False)
self.mock_object(cephfs_native, "ceph_volume_client",
MockVolumeClientModule)
self.mock_object(cephfs_native, "ceph_module_found", True)
self._driver = (
cephfs_native.CephFSNativeDriver(configuration=self.fake_conf))
self.mock_object(share_types, 'get_share_type_extra_specs',
mock.Mock(return_value={}))
def test_create_share(self):
expected_export_locations = {
'path': '1.2.3.4,5.6.7.8:/foo/bar',
'is_admin_only': False,
'metadata': {},
}
export_locations = self._driver.create_share(self._context,
self._share)
self.assertEqual(expected_export_locations, export_locations)
self._driver._volume_client.create_volume.assert_called_once_with(
self._driver._share_path(self._share),
size=self._share['size'] * units.Gi,
data_isolated=False)
def test_ensure_share(self):
self._driver.ensure_share(self._context,
self._share)
self._driver._volume_client.create_volume.assert_called_once_with(
self._driver._share_path(self._share),
size=self._share['size'] * units.Gi,
data_isolated=False)
def test_create_data_isolated(self):
self.mock_object(share_types, 'get_share_type_extra_specs',
mock.Mock(return_value={"cephfs:data_isolated": True})
)
self._driver.create_share(self._context, self._share)
self._driver._volume_client.create_volume.assert_called_once_with(
self._driver._share_path(self._share),
size=self._share['size'] * units.Gi,
data_isolated=True)
def test_delete_share(self):
self._driver.delete_share(self._context, self._share)
self._driver._volume_client.delete_volume.assert_called_once_with(
self._driver._share_path(self._share),
data_isolated=False)
self._driver._volume_client.purge_volume.assert_called_once_with(
self._driver._share_path(self._share),
data_isolated=False)
def test_delete_data_isolated(self):
self.mock_object(share_types, 'get_share_type_extra_specs',
mock.Mock(return_value={"cephfs:data_isolated": True})
)
self._driver.delete_share(self._context, self._share)
self._driver._volume_client.delete_volume.assert_called_once_with(
self._driver._share_path(self._share),
data_isolated=True)
self._driver._volume_client.purge_volume.assert_called_once_with(
self._driver._share_path(self._share),
data_isolated=True)
def test_allow_access(self):
access_rule = {
'access_level': constants.ACCESS_LEVEL_RW,
'access_type': 'cephx',
'access_to': 'alice'
}
self._driver._allow_access(self._context, self._share, access_rule)
self._driver._volume_client.authorize.assert_called_once_with(
self._driver._share_path(self._share),
"alice")
def test_allow_access_wrong_type(self):
self.assertRaises(exception.InvalidShareAccess,
self._driver._allow_access,
self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RW,
'access_type': 'RHUBARB',
'access_to': 'alice'
})
def test_allow_access_ro(self):
self.assertRaises(exception.InvalidShareAccessLevel,
self._driver._allow_access,
self._context, self._share, {
'access_level': constants.ACCESS_LEVEL_RO,
'access_type': 'cephx',
'access_to': 'alice'
})
def test_deny_access(self):
self._driver._deny_access(self._context, self._share, {
'access_level': 'rw',
'access_type': 'cephx',
'access_to': 'alice'
})
self._driver._volume_client.deauthorize.assert_called_once_with(
self._driver._share_path(self._share),
"alice")
def test_update_access_add_rm(self):
alice = {
'access_level': 'rw',
'access_type': 'cephx',
'access_to': 'alice'
}
bob = {
'access_level': 'rw',
'access_type': 'cephx',
'access_to': 'bob'
}
self._driver.update_access(self._context, self._share,
access_rules=[alice],
add_rules=[alice],
delete_rules=[bob])
self._driver._volume_client.authorize.assert_called_once_with(
self._driver._share_path(self._share),
"alice")
self._driver._volume_client.deauthorize.assert_called_once_with(
self._driver._share_path(self._share),
"bob")
def test_update_access_all(self):
alice = {
'access_level': 'rw',
'access_type': 'cephx',
'access_to': 'alice'
}
self._driver.update_access(self._context, self._share,
access_rules=[alice], add_rules=[],
delete_rules=[])
self._driver._volume_client.authorize.assert_called_once_with(
self._driver._share_path(self._share),
"alice")
def test_extend_share(self):
new_size_gb = self._share['size'] * 2
new_size = new_size_gb * units.Gi
self._driver.extend_share(self._share, new_size_gb, None)
self._driver._volume_client.set_max_bytes.assert_called_once_with(
self._driver._share_path(self._share),
new_size)
def test_shrink_share(self):
new_size_gb = self._share['size'] * 0.5
new_size = new_size_gb * units.Gi
self._driver.shrink_share(self._share, new_size_gb, None)
self._driver._volume_client.get_used_bytes.assert_called_once_with(
self._driver._share_path(self._share))
self._driver._volume_client.set_max_bytes.assert_called_once_with(
self._driver._share_path(self._share),
new_size)
def test_shrink_share_full(self):
"""That shrink fails when share is too full."""
new_size_gb = self._share['size'] * 0.5
# Pretend to be full up
vc = MockVolumeClientModule.CephFSVolumeClient
vc.mock_used_bytes = (units.Gi * self._share['size'])
self.assertRaises(exception.ShareShrinkingPossibleDataLoss,
self._driver.shrink_share,
self._share, new_size_gb, None)
self._driver._volume_client.set_max_bytes.assert_not_called()
def test_create_snapshot(self):
self._driver.create_snapshot(self._context,
{
"share": self._share,
"name": "snappy1"
},
None)
(self._driver._volume_client.create_snapshot_volume
.assert_called_once_with(
self._driver._share_path(self._share),
"snappy1"))
def test_delete_snapshot(self):
self._driver.delete_snapshot(self._context,
{
"share": self._share,
"name": "snappy1"
},
None)
(self._driver._volume_client.destroy_snapshot_volume
.assert_called_once_with(
self._driver._share_path(self._share),
"snappy1"))
def test_create_consistency_group(self):
self._driver.create_consistency_group(self._context, {"id": "grp1"},
None)
self._driver._volume_client.create_group.assert_called_once_with(
"grp1")
def test_delete_consistency_group(self):
self._driver.delete_consistency_group(self._context, {"id": "grp1"},
None)
self._driver._volume_client.destroy_group.assert_called_once_with(
"grp1")
def test_create_cg_snapshot(self):
self._driver.create_cgsnapshot(self._context, {
'consistency_group_id': 'cgid',
'id': 'snapid'
})
(self._driver._volume_client.create_snapshot_group.
assert_called_once_with("cgid", "snapid"))
def test_delete_cgsnapshot(self):
self._driver.delete_cgsnapshot(self._context, {
'consistency_group_id': 'cgid',
'id': 'snapid'
})
(self._driver._volume_client.destroy_snapshot_group.
assert_called_once_with("cgid", "snapid"))
def test_delete_driver(self):
# Create share to prompt volume_client construction
self._driver.create_share(self._context,
self._share)
vc = self._driver._volume_client
del self._driver
vc.disconnect.assert_called_once_with()
def test_delete_driver_no_client(self):
self.assertEqual(None, self._driver._volume_client)
del self._driver
def test_connect_noevict(self):
# When acting as "admin", driver should skip evicting
self._driver.configuration.local_conf.set_override('cephfs_auth_id',
"admin")
self._driver.create_share(self._context,
self._share)
vc = self._driver._volume_client
vc.connect.assert_called_once_with(premount_evict=None)
def test_update_share_stats(self):
self._driver._volume_client
self._driver._update_share_stats()
result = self._driver._stats
self.assertEqual("CEPHFS", result['storage_protocol'])
def test_module_missing(self):
cephfs_native.ceph_module_found = False
cephfs_native.ceph_volume_client = None
self.assertRaises(exception.ManilaException,
self._driver.create_share,
self._context,
self._share)

View File

@ -36,7 +36,7 @@ ShareGroup = [
help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion",
default="2.12",
default="2.13",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",
@ -73,6 +73,9 @@ ShareGroup = [
cfg.ListOpt("enable_cert_rules_for_protocols",
default=["glusterfs", ],
help="Protocols that should be covered with cert rule tests."),
cfg.ListOpt("enable_cephx_rules_for_protocols",
default=["cephfs", ],
help="Protocols to be covered with cephx rule tests."),
cfg.StrOpt("username_for_user_rules",
default="Administrator",
help="Username, that will be used in user tests."),

View File

@ -196,3 +196,7 @@ class ManageGLUSTERFSShareTest(ManageNFSShareTest):
class ManageHDFSShareTest(ManageNFSShareTest):
protocol = 'hdfs'
class ManageCephFSShareTest(ManageNFSShareTest):
protocol = 'cephfs'

View File

@ -86,7 +86,7 @@ class BaseSharesTest(test.BaseTestCase):
"""Base test case class for all Manila API tests."""
force_tenant_isolation = False
protocols = ["nfs", "cifs", "glusterfs", "hdfs"]
protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
# Will be cleaned up in resource_cleanup
class_resources = []

View File

@ -358,6 +358,41 @@ class ShareCertRulesForGLUSTERFSTest(base.BaseSharesTest):
rule_id=rule["id"], share_id=self.share['id'], version=version)
@ddt.ddt
class ShareCephxRulesForCephFSTest(base.BaseSharesTest):
protocol = "cephfs"
@classmethod
def resource_setup(cls):
super(ShareCephxRulesForCephFSTest, cls).resource_setup()
if (cls.protocol not in CONF.share.enable_protocols or
cls.protocol not in
CONF.share.enable_cephx_rules_for_protocols):
msg = ("Cephx rule tests for %s protocol are disabled." %
cls.protocol)
raise cls.skipException(msg)
cls.share = cls.create_share(cls.protocol)
cls.access_type = "cephx"
# Provide access to a client identified by a cephx auth id.
cls.access_to = "bob"
@test.attr(type=["gate", ])
@ddt.data("alice", "alice_bob", "alice bob")
def test_create_delete_cephx_rule(self, access_to):
rule = self.shares_v2_client.create_access_rule(
self.share["id"], self.access_type, access_to)
self.assertEqual('rw', rule['access_level'])
for key in ('deleted', 'deleted_at', 'instance_mappings'):
self.assertNotIn(key, rule.keys())
self.shares_v2_client.wait_for_access_rule_status(
self.share["id"], rule["id"], "active")
self.shares_v2_client.delete_access_rule(self.share["id"], rule["id"])
self.shares_v2_client.wait_for_resource_deletion(
rule_id=rule["id"], share_id=self.share['id'])
@ddt.ddt
class ShareRulesTest(base.BaseSharesTest):
@ -369,6 +404,8 @@ class ShareRulesTest(base.BaseSharesTest):
any(p in CONF.share.enable_user_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cert_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cephx_rules_for_protocols
for p in cls.protocols)):
cls.message = "Rule tests are disabled"
raise cls.skipException(cls.message)
@ -384,12 +421,21 @@ class ShareRulesTest(base.BaseSharesTest):
cls.protocol = CONF.share.enable_cert_rules_for_protocols[0]
cls.access_type = "cert"
cls.access_to = "client3.com"
elif CONF.share.enable_cephx_rules_for_protocols:
cls.protocol = CONF.share.enable_cephx_rules_for_protocols[0]
cls.access_type = "cephx"
cls.access_to = "alice"
cls.shares_v2_client.share_protocol = cls.protocol
cls.share = cls.create_share()
@test.attr(type=["gate", ])
@ddt.data('1.0', '2.9', LATEST_MICROVERSION)
def test_list_access_rules(self, version):
if (utils.is_microversion_lt(version, '2.13') and
CONF.share.enable_cephx_rules_for_protocols):
msg = ("API version %s does not support cephx access type, "
"need version greater than 2.13." % version)
raise self.skipException(msg)
# create rule
if utils.is_microversion_eq(version, '1.0'):
@ -447,6 +493,11 @@ class ShareRulesTest(base.BaseSharesTest):
@test.attr(type=["gate", ])
@ddt.data('1.0', '2.9', LATEST_MICROVERSION)
def test_access_rules_deleted_if_share_deleted(self, version):
if (utils.is_microversion_lt(version, '2.13') and
CONF.share.enable_cephx_rules_for_protocols):
msg = ("API version %s does not support cephx access type, "
"need version greater than 2.13." % version)
raise self.skipException(msg)
# create share
share = self.create_share()

View File

@ -19,6 +19,7 @@ from tempest import test
from tempest_lib import exceptions as lib_exc
import testtools
from manila_tempest_tests import share_exceptions
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
@ -316,6 +317,48 @@ class ShareCertRulesForGLUSTERFSNegativeTest(base.BaseSharesTest):
access_to="fakeclient2.com")
@ddt.ddt
class ShareCephxRulesForCephFSNegativeTest(base.BaseSharesTest):
protocol = "cephfs"
@classmethod
def resource_setup(cls):
super(ShareCephxRulesForCephFSNegativeTest, cls).resource_setup()
if not (cls.protocol in CONF.share.enable_protocols and
cls.protocol in CONF.share.enable_cephx_rules_for_protocols):
msg = ("CEPHX rule tests for %s protocol are disabled" %
cls.protocol)
raise cls.skipException(msg)
# create share
cls.share = cls.create_share(cls.protocol)
cls.access_type = "cephx"
cls.access_to = "david"
@test.attr(type=["negative", "gate", ])
@ddt.data('jane.doe', u"bj\u00F6rn")
def test_create_access_rule_cephx_with_invalid_cephx_id(self, access_to):
self.assertRaises(lib_exc.BadRequest,
self.shares_v2_client.create_access_rule,
self.share["id"], self.access_type, access_to)
@test.attr(type=["negative", "gate", ])
def test_create_access_rule_cephx_with_wrong_level(self):
self.assertRaises(lib_exc.BadRequest,
self.shares_v2_client.create_access_rule,
self.share["id"], self.access_type, self.access_to,
access_level="su")
@test.attr(type=["negative", "gate", ])
def test_create_access_rule_cephx_with_unsupported_access_level_ro(self):
rule = self.shares_v2_client.create_access_rule(
self.share["id"], self.access_type, self.access_to,
access_level="ro")
self.assertRaises(
share_exceptions.AccessRuleBuildErrorException,
self.shares_client.wait_for_access_rule_status,
self.share['id'], rule['id'], "active")
@ddt.ddt
class ShareRulesNegativeTest(base.BaseSharesTest):
# Tests independent from rule type and share protocol
@ -328,6 +371,8 @@ class ShareRulesNegativeTest(base.BaseSharesTest):
any(p in CONF.share.enable_user_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cert_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cephx_rules_for_protocols
for p in cls.protocols)):
cls.message = "Rule tests are disabled"
raise cls.skipException(cls.message)
@ -337,9 +382,21 @@ class ShareRulesNegativeTest(base.BaseSharesTest):
# create snapshot
cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"])
def skip_if_cephx_access_type_not_supported_by_client(self, client):
if client == 'shares_client':
version = '1.0'
else:
version = LATEST_MICROVERSION
if (CONF.share.enable_cephx_rules_for_protocols and
utils.is_microversion_lt(version, '2.13')):
msg = ("API version %s does not support cephx access type, "
"need version greater than 2.13." % version)
raise self.skipException(msg)
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_delete_access_rule_with_wrong_id(self, client_name):
self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).delete_access_rule,
self.share["id"], "wrong_rule_id")
@ -347,6 +404,7 @@ class ShareRulesNegativeTest(base.BaseSharesTest):
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_create_access_rule_ip_with_wrong_type(self, client_name):
self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.BadRequest,
getattr(self, client_name).create_access_rule,
self.share["id"], "wrong_type", "1.2.3.4")
@ -354,6 +412,7 @@ class ShareRulesNegativeTest(base.BaseSharesTest):
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_create_access_rule_ip_with_wrong_share_id(self, client_name):
self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).create_access_rule,
"wrong_share_id")
@ -363,6 +422,7 @@ class ShareRulesNegativeTest(base.BaseSharesTest):
@testtools.skipUnless(CONF.share.run_snapshot_tests,
"Snapshot tests are disabled.")
def test_create_access_rule_ip_to_snapshot(self, client_name):
self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).create_access_rule,
self.snap["id"])

View File

@ -187,3 +187,8 @@ class SharesGLUSTERFSTest(SharesNFSTest):
class SharesHDFSTest(SharesNFSTest):
"""Covers share functionality that is related to HDFS share type."""
protocol = "hdfs"
class SharesCephFSTest(SharesNFSTest):
"""Covers share functionality that is related to CEPHFS share type."""
protocol = "cephfs"