Skip to content

Commit

Permalink
ganesha: NFS-Ganesha instrumentation
Browse files Browse the repository at this point in the history
Introduce the ganesha share driver helper module
which provides the GaneshaNASHelper class from which
share drivers can derive NFS-Ganesha backed protocol
helpers.

Some utility functions are also added to ease
integration.

Partially implements blueprint gateway-mediated-with-ganesha

Change-Id: I8683ea5eb43d7a8eaf0dfa6af3791782d32b944a
  • Loading branch information
csabahenk committed Dec 18, 2014
1 parent 28f311c commit 559b478
Show file tree
Hide file tree
Showing 13 changed files with 1,533 additions and 18 deletions.
17 changes: 17 additions & 0 deletions etc/manila/rootwrap.d/share.filters
Expand Up @@ -3,6 +3,7 @@

[Filters]
# manila/share/drivers/glusterfs.py: 'mkdir', '%s'
# manila/share/drivers/ganesha/manager.py: 'mkdir', '-p', '%s'
mkdir: CommandFilter, /usr/bin/mkdir, root

# manila/share/drivers/glusterfs.py: 'rm', '-rf', '%s'
Expand Down Expand Up @@ -49,6 +50,7 @@ rsync: CommandFilter, /usr/bin/rsync, root
exportfs: CommandFilter, /usr/sbin/exportfs, root
# Ganesha commands
# manila/share/drivers/ibm/ganesha_utils.py: 'mv', '%s', '%s'
# manila/share/drivers/ganesha/manager.py: 'mv', '%s', '%s'
mv: CommandFilter, /bin/mv, root
# manila/share/drivers/ibm/ganesha_utils.py: 'cp', '%s', '%s'
cp: CommandFilter, /bin/cp, root
Expand All @@ -60,3 +62,18 @@ ssh: CommandFilter, /usr/bin/ssh, root
chmod: CommandFilter, /bin/chmod, root
# manila/share/drivers/ibm/ganesha_utils.py: 'service', '%s', 'restart'
service: CommandFilter, /sbin/service, root

# manila/share/drivers/ganesha/manager.py: 'mktemp', '-p', '%s', '-t', '%s'
mktemp: CommandFilter, /bin/mktemp, root

# manila/share/drivers/ganesha/manager.py:
shcat: RegExpFilter, /bin/sh, root, sh, -c, cat > /.*

# manila/share/drivers/ganesha/manager.py:
dbus-addexport: RegExpFilter, /usr/bin/dbus-send, root, dbus-send, --print-reply, --system, --dest=org\.ganesha\.nfsd, /org/ganesha/nfsd/ExportMgr, org\.ganesha\.nfsd\.exportmgr\.(Add|Remove)Export, .*, .*

# manila/share/drivers/ganesha/manager.py:
dbus-removeexport: RegExpFilter, /usr/bin/dbus-send, root, dbus-send, --print-reply, --system, --dest=org\.ganesha\.nfsd, /org/ganesha/nfsd/ExportMgr, org\.ganesha\.nfsd\.exportmgr\.(Add|Remove)Export, .*

# manila/share/drivers/ganesha/manager.py:
rmconf: RegExpFilter, /bin/sh, root, sh, -c, rm /.*/\*\.conf$
13 changes: 13 additions & 0 deletions manila/exception.py
Expand Up @@ -464,3 +464,16 @@ class GPFSException(ManilaException):

class GPFSGaneshaException(ManilaException):
message = _("GPFS Ganesha exception occurred.")


class GaneshaCommandFailure(ProcessExecutionError):
_description = _("Ganesha management command failed.")

def __init__(self, **kw):
if 'description' not in kw:
kw['description'] = self._description
super(GaneshaCommandFailure, self).__init__(**kw)


class InvalidSqliteDB(Invalid):
message = _("Invalid Sqlite database.")
1 change: 1 addition & 0 deletions manila/opts.py
Expand Up @@ -95,6 +95,7 @@
manila.scheduler.weights.capacity.capacity_weight_opts,
manila.service.service_opts,
manila.share.api.share_api_opts,
manila.share.driver.ganesha_opts,
manila.share.driver.share_opts,
manila.share.driver.ssh_opts,
manila.share.drivers.emc.driver.EMC_NAS_OPTS,
Expand Down
42 changes: 42 additions & 0 deletions manila/share/driver.py
Expand Up @@ -77,9 +77,40 @@
help='Maximum number of connections in the SSH pool.'),
]

ganesha_opts = [
cfg.StrOpt('ganesha_config_dir',
default='/etc/ganesha',
help='Directory where Ganesha config files are stored.'),
cfg.StrOpt('ganesha_config_path',
default='$ganesha_config_dir/ganesha.conf',
help='Path to main Ganesha config file.'),
cfg.StrOpt('ganesha_nfs_export_options',
default='maxread = 65536, prefread = 65536',
help='Options to use when exporting a share using ganesha '
'NFS server. Note that these defaults can be overridden '
'when a share is created by passing metadata with key '
'name export_options. Also note the complete set of '
'default ganesha export options is specified in '
'ganesha_utils. (GPFS only.)'),
cfg.StrOpt('ganesha_service_name',
default='ganesha.nfsd',
help='Name of the ganesha nfs service.'),
cfg.StrOpt('ganesha_db_path',
default='$state_path/manila-ganesha.db',
help='Location of Ganesha database file. '
'(Ganesha module only.)'),
cfg.StrOpt('ganesha_export_dir',
default='$ganesha_config_dir/export.d',
help='Path to Ganesha export template. (Ganesha module only.)'),
cfg.StrOpt('ganesha_export_template_dir',
default='/etc/manila/ganesha-export-templ.d',
help='Path to Ganesha export template. (Ganesha module only.)'),
]

CONF = cfg.CONF
CONF.register_opts(share_opts)
CONF.register_opts(ssh_opts)
CONF.register_opts(ganesha_opts)


class ExecuteMixin(object):
Expand Down Expand Up @@ -111,6 +142,14 @@ def _try_execute(self, *command, **kwargs):
time.sleep(tries ** 2)


class GaneshaMixin(object):
"""Augment derived classes with Ganesha configuration."""

def init_ganesha_mixin(self, *args, **kwargs):
if self.configuration:
self.configuration.append_config_values(ganesha_opts)


class ShareDriver(object):
"""Class defines interface of NAS driver."""

Expand All @@ -129,6 +168,9 @@ def __init__(self, *args, **kwargs):
if hasattr(self, 'init_execute_mixin'):
# Instance with 'ExecuteMixin'
self.init_execute_mixin(*args, **kwargs) # pylint: disable=E1101
if hasattr(self, 'init_ganesha_mixin'):
# Instance with 'GaneshaMixin'
self.init_execute_mixin(*args, **kwargs) # pylint: disable=E1101
self.network_api = network.API(config_group_name=network_config_group)

def _validate_driver_mode(self, mode):
Expand Down
141 changes: 141 additions & 0 deletions manila/share/drivers/ganesha/__init__.py
@@ -0,0 +1,141 @@
# Copyright (c) 2014 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 abc
import errno
import os
import re

from oslo.config import cfg
import six

from manila import exception
from manila.i18n import _LI
from manila.openstack.common import log as logging
from manila.share.drivers.ganesha import manager as ganesha_manager
from manila.share.drivers.ganesha import utils as ganesha_utils


CONF = cfg.CONF
LOG = logging.getLogger(__name__)


@six.add_metaclass(abc.ABCMeta)
class NASHelperBase(object):
"""Interface to work with share."""

def __init__(self, execute, config, **kwargs):
self.configuration = config
self._execute = execute

def init_helper(self):
"""Initializes protocol-specific NAS drivers."""

@abc.abstractmethod
def allow_access(self, base_path, share, access):
"""Allow access to the host."""

@abc.abstractmethod
def deny_access(self, base_path, share, access):
"""Deny access to the host."""


class GaneshaNASHelper(NASHelperBase):
"""Execute commands relating to Shares."""

def __init__(self, execute, config, tag='<no name>', **kwargs):
super(GaneshaNASHelper, self).__init__(execute, config, **kwargs)
self.tag = tag

confrx = re.compile('\.(conf|json)\Z')

def _load_conf_dir(self, dirpath, must_exist=True):
"""Load Ganesha config files in dirpath in alphabetic order."""
try:
dirlist = os.listdir(dirpath)
except OSError as e:
if e.errno != errno.ENOENT or must_exist:
raise
dirlist = []
LOG.info(_LI('Loading Ganesha config from %s.'), dirpath)
conf_files = filter(self.confrx.search, dirlist)
conf_files.sort()
export_template = {}
for conf_file in conf_files:
with open(os.path.join(dirpath, conf_file)) as f:
ganesha_utils.patch(
export_template,
ganesha_manager.parseconf(f.read()))
return export_template

def init_helper(self):
"""Initializes protocol-specific NAS drivers."""
self.ganesha = ganesha_manager.GaneshaManager(
self._execute,
self.tag,
ganesha_config_path=self.configuration.ganesha_config_path,
ganesha_export_dir=self.configuration.ganesha_export_dir,
ganesha_db_path=self.configuration.ganesha_db_path,
ganesha_service_name=self.configuration.ganesha_service_name)
system_export_template = self._load_conf_dir(
self.configuration.ganesha_export_template_dir,
must_exist=False)
if system_export_template:
self.export_template = system_export_template
else:
self.export_template = self._default_config_hook()

def _default_config_hook(self):
"""The default export block.
Subclass this to add FSAL specific defaults.
Suggested approach: take the return value of superclass'
method, patch with dict containing your defaults, and
return the result. However, you can also provide your
defaults from scratch with no regard to superclass.
"""

return self._load_conf_dir(ganesha_utils.path_from(__file__, "conf"))

def _fsal_hook(self, base_path, share, access):
"""Subclass this to create FSAL block."""
return {}

def allow_access(self, base_path, share, access):
"""Allow access to the share."""
if access['access_type'] != 'ip':
raise exception.InvalidShareAccess('Only IP access type allowed')
cf = {}
accid = access['id']
name = share['name']
export_name = "%s--%s" % (name, accid)
ganesha_utils.patch(cf, self.export_template, {
'EXPORT': {
'Export_Id': self.ganesha.get_export_id(),
'Path': os.path.join(base_path, name),
'Pseudo': os.path.join(base_path, export_name),
'Tag': accid,
'CLIENT': {
'Clients': access['access_to']
},
'FSAL': self._fsal_hook(base_path, share, access)
}
})
self.ganesha.add_export(export_name, cf)

def deny_access(self, base_path, share, access):
"""Deny access to the share."""
self.ganesha.remove_export("%s--%s" % (share['name'], access['id']))
48 changes: 48 additions & 0 deletions manila/share/drivers/ganesha/conf/00-base-export-template.conf
@@ -0,0 +1,48 @@
# This is a Ganesha config template.
# Syntactically, a valid Ganesha config
# file, but some values in it are stubs.
# Fields that have stub values are managed
# by Manila; the stubs are of two kinds:
# - @config:
# value will be taken from Manila config
# - @runtime:
# value will be determined at runtime
# User is free to set Ganesha parameters
# which are not reserved to Manila by
# stubbing.

EXPORT {
# Each EXPORT must have a unique Export_Id.
Export_Id = @runtime;

# The directory in the exported file system this export
# is rooted on.
Path = @runtime;

# FSAL, Ganesha's module component
FSAL {
# FSAL name
Name = @config;
}

# Path of export in the NFSv4 pseudo filesystem
Pseudo = @runtime;

# RPC security flavor, one of none, sys, krb5{,i,p}
SecType = sys;

# Alternative export identifier for NFSv3
Tag = @runtime;

# Client specification
CLIENT {
# Comma separated list of clients
Clients = @runtime;

# Access type, one of RW, RO, MDONLY, MDONLY_RO, NONE
Access_Type = RW;
}

# User id squashing, one of None, Root, All
Squash = None;
}

0 comments on commit 559b478

Please sign in to comment.