Coverage for manila/share/drivers/glusterfs/__init__.py: 98%
154 statements
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2026-02-18 22:19 +0000
1# Copyright (c) 2013 Red Hat, Inc.
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
16"""Flat network GlusterFS Driver.
18Manila shares are subdirectories within a GlusterFS volume. The backend,
19a GlusterFS cluster, uses one of the two NFS servers, Gluster-NFS or
20NFS-Ganesha, based on a configuration option, to mediate access to the shares.
21NFS-Ganesha server supports NFSv3 and v4 protocols, while Gluster-NFS
22server supports only NFSv3 protocol.
24TODO(rraja): support SMB protocol.
25"""
27import re
28import socket
29import sys
31from oslo_config import cfg
32from oslo_log import log
34from manila.common import constants
35from manila import exception
36from manila.i18n import _
37from manila.share import driver
38from manila.share.drivers import ganesha
39from manila.share.drivers.ganesha import utils as ganesha_utils
40from manila.share.drivers.glusterfs import layout
41from manila import utils
44GlusterfsManilaShare_opts = [
45 cfg.StrOpt('glusterfs_nfs_server_type',
46 default='Gluster',
47 help='Type of NFS server that mediate access to the Gluster '
48 'volumes (Gluster or Ganesha).'),
49 cfg.HostAddressOpt('glusterfs_ganesha_server_ip',
50 help="Remote Ganesha server node's IP address."),
51 cfg.StrOpt('glusterfs_ganesha_server_username',
52 default='root',
53 help="Remote Ganesha server node's username."),
54 cfg.StrOpt('glusterfs_ganesha_server_password',
55 secret=True,
56 help="Remote Ganesha server node's login password. "
57 "This is not required if 'glusterfs_path_to_private_key'"
58 ' is configured.'),
59]
61CONF = cfg.CONF
62CONF.register_opts(GlusterfsManilaShare_opts)
64LOG = log.getLogger(__name__)
67NFS_EXPORT_DIR = 'nfs.export-dir'
68NFS_EXPORT_VOL = 'nfs.export-volumes'
69NFS_RPC_AUTH_ALLOW = 'nfs.rpc-auth-allow'
70NFS_RPC_AUTH_REJECT = 'nfs.rpc-auth-reject'
73class GlusterfsShareDriver(driver.ExecuteMixin, driver.GaneshaMixin,
74 layout.GlusterfsShareDriverBase):
75 """Execute commands relating to Shares."""
77 GLUSTERFS_VERSION_MIN = (3, 5)
79 supported_layouts = ('layout_directory.GlusterfsDirectoryMappedLayout',
80 'layout_volume.GlusterfsVolumeMappedLayout')
81 supported_protocols = ('NFS',)
83 def __init__(self, *args, **kwargs):
84 super(GlusterfsShareDriver, self).__init__(False, *args, **kwargs)
85 LOG.warning('GlusterFS share driver has been deprecated and is '
86 'expected to be removed in a future release.')
87 self._helpers = {}
88 self.configuration.append_config_values(GlusterfsManilaShare_opts)
89 self.backend_name = self.configuration.safe_get(
90 'share_backend_name') or 'GlusterFS'
91 self.nfs_helper = getattr(
92 sys.modules[__name__],
93 self.configuration.glusterfs_nfs_server_type + 'NFSHelper')
95 def do_setup(self, context):
96 # in order to do an initial instantialization of the helper
97 self._get_helper()
98 super(GlusterfsShareDriver, self).do_setup(context)
100 def _setup_via_manager(self, share_manager, share_manager_parent=None):
101 gluster_manager = share_manager['manager']
102 # TODO(csaba): This should be refactored into proper dispatch to helper
103 if self.nfs_helper == GlusterNFSHelper and not gluster_manager.path:
104 # default behavior of NFS_EXPORT_VOL is as if it were 'on'
105 export_vol = gluster_manager.get_vol_option(
106 NFS_EXPORT_VOL, boolean=True)
107 if export_vol is False:
108 raise exception.GlusterfsException(
109 _("Gluster-NFS with volume layout should be used "
110 "with `nfs.export-volumes = on`"))
111 setting = [NFS_RPC_AUTH_REJECT, '*']
112 else:
113 # gluster-nfs export of the whole volume must be prohibited
114 # to not to defeat access control
115 setting = [NFS_EXPORT_VOL, False]
116 gluster_manager.set_vol_option(*setting)
117 return self.nfs_helper(self._execute, self.configuration,
118 gluster_manager=gluster_manager).get_export(
119 share_manager['share'])
121 def check_for_setup_error(self):
122 pass
124 def _update_share_stats(self):
125 """Retrieve stats info from the GlusterFS volume."""
127 data = dict(
128 storage_protocol='NFS',
129 vendor_name='Red Hat',
130 share_backend_name=self.backend_name,
131 reserved_percentage=self.configuration.reserved_share_percentage,
132 reserved_snapshot_percentage=(
133 self.configuration.reserved_share_from_snapshot_percentage
134 or self.configuration.reserved_share_percentage),
135 reserved_share_extend_percentage=(
136 self.configuration.reserved_share_extend_percentage
137 or self.configuration.reserved_share_percentage))
138 super(GlusterfsShareDriver, self)._update_share_stats(data)
140 def get_network_allocations_number(self):
141 return 0
143 def _get_helper(self, gluster_mgr=None):
144 """Choose a protocol specific helper class."""
145 helper_class = self.nfs_helper
146 if (self.nfs_helper == GlusterNFSHelper and gluster_mgr and
147 not gluster_mgr.path):
148 helper_class = GlusterNFSVolHelper
149 helper = helper_class(self._execute, self.configuration,
150 gluster_manager=gluster_mgr)
151 helper.init_helper()
152 return helper
154 @property
155 def supported_access_types(self):
156 return self.nfs_helper.supported_access_types
158 @property
159 def supported_access_levels(self):
160 return self.nfs_helper.supported_access_levels
162 def _update_access_via_manager(self, gluster_mgr, context, share,
163 add_rules, delete_rules, recovery=False,
164 share_server=None):
165 """Update access to the share."""
166 self._get_helper(gluster_mgr).update_access(
167 '/', share, add_rules, delete_rules, recovery=recovery)
170class GlusterNFSHelper(ganesha.NASHelperBase):
171 """Manage shares with Gluster-NFS server."""
173 supported_access_types = ('ip', )
174 supported_access_levels = (constants.ACCESS_LEVEL_RW, )
176 def __init__(self, execute, config_object, **kwargs):
177 self.gluster_manager = kwargs.pop('gluster_manager')
178 super(GlusterNFSHelper, self).__init__(execute, config_object,
179 **kwargs)
181 def get_export(self, share):
182 return self.gluster_manager.export
184 def _get_export_dir_dict(self):
185 """Get the export entries of shares in the GlusterFS volume."""
186 export_dir = self.gluster_manager.get_vol_option(
187 NFS_EXPORT_DIR)
188 edh = {}
189 if export_dir:
190 # see
191 # https://github.com/gluster/glusterfs
192 # /blob/aa19909/xlators/nfs/server/src/nfs.c#L1582
193 # regarding the format of nfs.export-dir
194 edl = export_dir.split(',')
195 # parsing export_dir into a dict of {dir: [hostpec,..]..}
196 # format
197 r = re.compile(r'\A/(.*)\((.*)\)\Z')
198 for ed in edl:
199 d, e = r.match(ed).groups()
200 edh[d] = e.split('|')
201 return edh
203 def update_access(self, base_path, share, add_rules, delete_rules,
204 recovery=False):
205 """Update access rules."""
207 existing_rules_set = set()
209 # The name of the directory, which is exported as the share.
210 export_dir = self.gluster_manager.path[1:]
212 # Fetch the existing export entries as an export dictionary with the
213 # exported directories and the list of client IP addresses authorized
214 # to access them as key-value pairs.
215 export_dir_dict = self._get_export_dir_dict()
217 if export_dir in export_dir_dict:
218 existing_rules_set = set(export_dir_dict[export_dir])
219 add_rules_set = {rule['access_to'] for rule in add_rules}
220 delete_rules_set = {rule['access_to'] for rule in delete_rules}
221 new_rules_set = (
222 (existing_rules_set | add_rules_set) - delete_rules_set)
224 if new_rules_set:
225 export_dir_dict[export_dir] = new_rules_set
226 elif export_dir not in export_dir_dict:
227 return
228 else:
229 export_dir_dict.pop(export_dir)
231 # Reconstruct the export entries.
232 if export_dir_dict:
233 export_dirs_new = (",".join("/%s(%s)" % (d, "|".join(sorted(v)))
234 for d, v in sorted(export_dir_dict.items())))
235 else:
236 export_dirs_new = None
237 self.gluster_manager.set_vol_option(NFS_EXPORT_DIR, export_dirs_new)
240class GlusterNFSVolHelper(GlusterNFSHelper):
241 """Manage shares with Gluster-NFS server, volume mapped variant."""
243 def _get_vol_exports(self):
244 export_vol = self.gluster_manager.get_vol_option(
245 NFS_RPC_AUTH_ALLOW)
246 return export_vol.split(',') if export_vol else []
248 def update_access(self, base_path, share, add_rules, delete_rules,
249 recovery=False):
250 """Update access rules."""
252 existing_rules_set = set(self._get_vol_exports())
253 add_rules_set = {rule['access_to'] for rule in add_rules}
254 delete_rules_set = {rule['access_to'] for rule in delete_rules}
255 new_rules_set = (
256 (existing_rules_set | add_rules_set) - delete_rules_set)
258 if new_rules_set:
259 argseq = ((NFS_RPC_AUTH_ALLOW, ','.join(sorted(new_rules_set))),
260 (NFS_RPC_AUTH_REJECT, None))
261 else:
262 argseq = ((NFS_RPC_AUTH_ALLOW, None),
263 (NFS_RPC_AUTH_REJECT, '*'))
265 for args in argseq:
266 self.gluster_manager.set_vol_option(*args)
269class GaneshaNFSHelper(ganesha.GaneshaNASHelper):
271 shared_data = {}
273 def __init__(self, execute, config_object, **kwargs):
274 self.gluster_manager = kwargs.pop('gluster_manager')
275 if config_object.glusterfs_ganesha_server_ip:
276 execute = ganesha_utils.SSHExecutor(
277 config_object.glusterfs_ganesha_server_ip, 22, None,
278 config_object.glusterfs_ganesha_server_username,
279 password=config_object.glusterfs_ganesha_server_password,
280 privatekey=config_object.glusterfs_path_to_private_key)
281 else:
282 execute = ganesha_utils.RootExecutor(execute)
283 self.ganesha_host = config_object.glusterfs_ganesha_server_ip
284 if not self.ganesha_host:
285 self.ganesha_host = socket.gethostname()
286 kwargs['tag'] = '-'.join(('GLUSTER', 'Ganesha', self.ganesha_host))
287 super(GaneshaNFSHelper, self).__init__(execute, config_object,
288 **kwargs)
290 def get_export(self, share):
291 return ':/'.join((self.ganesha_host, share['name'] + "--<access-id>"))
293 def init_helper(self):
294 @utils.synchronized(self.tag)
295 def _init_helper():
296 if self.tag in self.shared_data:
297 return True
298 super(GaneshaNFSHelper, self).init_helper()
299 self.shared_data[self.tag] = {
300 'ganesha': self.ganesha,
301 'export_template': self.export_template}
302 return False
304 if _init_helper():
305 tagdata = self.shared_data[self.tag]
306 self.ganesha = tagdata['ganesha']
307 self.export_template = tagdata['export_template']
309 def _default_config_hook(self):
310 """Callback to provide default export block."""
311 dconf = super(GaneshaNFSHelper, self)._default_config_hook()
312 conf_dir = ganesha_utils.path_from(__file__, "conf")
313 ganesha_utils.patch(dconf, self._load_conf_dir(conf_dir))
314 return dconf
316 def _fsal_hook(self, base, share, access):
317 """Callback to create FSAL subblock."""
318 return {"Hostname": self.gluster_manager.host,
319 "Volume": self.gluster_manager.volume,
320 "Volpath": self.gluster_manager.path}
322 def update_access(self, base_path, share, add_rules, delete_rules,
323 recovery=False):
324 """Update access rules."""
326 context = None
327 access_rules = []
328 super(GaneshaNFSHelper, self).update_access(
329 context, share, access_rules, add_rules,
330 delete_rules, share_server=None)