Coverage for manila/share/drivers/glusterfs/layout_directory.py: 100%
132 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) 2015 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"""GlusterFS directory mapped share layout."""
18import math
19import os
21from defusedxml import ElementTree as etree
22from oslo_config import cfg
23from oslo_log import log
25from manila import exception
26from manila.i18n import _
27from manila.privsep import os as privsep_os
28from manila.share.drivers.glusterfs import common
29from manila.share.drivers.glusterfs import layout
30from manila import utils
32LOG = log.getLogger(__name__)
34glusterfs_directory_mapped_opts = [
35 cfg.StrOpt('glusterfs_target',
36 help='Specifies the GlusterFS volume to be mounted on the '
37 'Manila host. It is of the form '
38 '[remoteuser@]<volserver>:<volid>.'),
39 cfg.StrOpt('glusterfs_mount_point_base',
40 default='$state_path/mnt',
41 help='Base directory containing mount points for Gluster '
42 'volumes.'),
43]
45CONF = cfg.CONF
46CONF.register_opts(glusterfs_directory_mapped_opts)
49class GlusterfsDirectoryMappedLayout(layout.GlusterfsShareLayoutBase):
51 def __init__(self, driver, *args, **kwargs):
52 super(GlusterfsDirectoryMappedLayout, self).__init__(
53 driver, *args, **kwargs)
54 self.configuration.append_config_values(
55 common.glusterfs_common_opts)
56 self.configuration.append_config_values(
57 glusterfs_directory_mapped_opts)
59 def _glustermanager(self, gluster_address):
60 """Create GlusterManager object for gluster_address."""
62 return common.GlusterManager(
63 gluster_address, self.driver._execute,
64 self.configuration.glusterfs_path_to_private_key,
65 self.configuration.glusterfs_server_password,
66 requires={'volume': True})
68 def do_setup(self, context):
69 """Prepares the backend and appropriate NAS helpers."""
70 if not self.configuration.glusterfs_target:
71 raise exception.GlusterfsException(
72 _('glusterfs_target configuration that specifies the GlusterFS'
73 ' volume to be mounted on the Manila host is not set.'))
74 self.gluster_manager = self._glustermanager(
75 self.configuration.glusterfs_target)
76 self.gluster_manager.check_gluster_version(
77 self.driver.GLUSTERFS_VERSION_MIN)
78 self._check_mount_glusterfs()
80 # enable quota options of a GlusteFS volume to allow
81 # creation of shares of specific size
82 args = ('volume', 'quota', self.gluster_manager.volume, 'enable')
83 try:
84 self.gluster_manager.gluster_call(*args)
85 except exception.GlusterfsException:
86 if (self.gluster_manager.
87 get_vol_option('features.quota')) != 'on':
88 LOG.exception("Error in tuning GlusterFS volume to enable "
89 "creation of shares of specific size.")
90 raise
92 self._ensure_gluster_vol_mounted()
94 def _share_manager(self, share):
95 comp_path = self.gluster_manager.components.copy()
96 comp_path.update({'path': '/' + share['name']})
97 return self._glustermanager(comp_path)
99 def _get_mount_point_for_gluster_vol(self):
100 """Return mount point for the GlusterFS volume."""
101 return os.path.join(self.configuration.glusterfs_mount_point_base,
102 self.gluster_manager.volume)
104 def _ensure_gluster_vol_mounted(self):
105 """Ensure GlusterFS volume is native-mounted on Manila host."""
106 mount_path = self._get_mount_point_for_gluster_vol()
107 try:
108 common._mount_gluster_vol(self.driver._execute,
109 self.gluster_manager.export, mount_path,
110 ensure=True)
111 except exception.GlusterfsException:
112 LOG.exception('Could not mount the Gluster volume %s',
113 self.gluster_manager.volume)
114 raise
116 def _get_local_share_path(self, share):
117 """Determine mount path of the GlusterFS volume in the Manila host."""
118 local_vol_path = self._get_mount_point_for_gluster_vol()
119 if not os.access(local_vol_path, os.R_OK):
120 raise exception.GlusterfsException('share path %s does not exist' %
121 local_vol_path)
122 return os.path.join(local_vol_path, share['name'])
124 def _update_share_stats(self):
125 """Retrieve stats info from the GlusterFS volume."""
127 # sanity check for gluster ctl mount
128 smpb = os.stat(self.configuration.glusterfs_mount_point_base)
129 smp = os.stat(self._get_mount_point_for_gluster_vol())
130 if smpb.st_dev == smp.st_dev:
131 raise exception.GlusterfsException(
132 _("GlusterFS control mount is not available")
133 )
134 smpv = os.statvfs(self._get_mount_point_for_gluster_vol())
136 return {'total_capacity_gb': (smpv.f_blocks * smpv.f_frsize) >> 30,
137 'free_capacity_gb': (smpv.f_bavail * smpv.f_frsize) >> 30}
139 def create_share(self, ctx, share, share_server=None):
140 """Create a sub-directory/share in the GlusterFS volume."""
141 # probe into getting a NAS protocol helper for the share in order
142 # to facilitate early detection of unsupported protocol type
143 local_share_path = self._get_local_share_path(share)
145 try:
146 privsep_os.mkdir(local_share_path)
147 self._set_directory_quota(share, share['size'])
148 except Exception as exc:
149 if isinstance(exc, exception.ProcessExecutionError):
150 exc = exception.GlusterfsException(exc)
151 if isinstance(exc, exception.GlusterfsException):
152 self._cleanup_create_share(local_share_path, share['name'])
153 LOG.error('Unable to create share %s', share['name'])
154 raise exc
156 comp_share = self.gluster_manager.components.copy()
157 comp_share['path'] = '/' + share['name']
158 export_location = self.driver._setup_via_manager(
159 {'share': share,
160 'manager': self._glustermanager(comp_share)})
162 return export_location
164 def _cleanup_create_share(self, share_path, share_name):
165 """Cleanup share that errored out during its creation."""
166 if os.path.exists(share_path):
167 try:
168 privsep_os.recursive_forced_rm(share_path)
169 except exception.ProcessExecutionError as exc:
170 LOG.error('Cannot cleanup share, %s, that errored out '
171 'during its creation, but exists in GlusterFS '
172 'volume.', share_name)
173 raise exception.GlusterfsException(exc)
175 def delete_share(self, context, share, share_server=None):
176 """Remove a sub-directory/share from the GlusterFS volume."""
177 local_share_path = self._get_local_share_path(share)
178 try:
179 privsep_os.recursive_forced_rm(local_share_path)
180 except exception.ProcessExecutionError:
181 LOG.exception('Unable to delete share %s', share['name'])
182 raise
184 def ensure_share(self, context, share, share_server=None):
185 pass
187 def create_share_from_snapshot(self, context, share, snapshot,
188 share_server=None, parent_share=None):
189 raise NotImplementedError
191 def create_snapshot(self, context, snapshot, share_server=None):
192 raise NotImplementedError
194 def delete_snapshot(self, context, snapshot, share_server=None):
195 raise NotImplementedError
197 def manage_existing(self, share, driver_options):
198 raise NotImplementedError
200 def unmanage(self, share):
201 raise NotImplementedError
203 def extend_share(self, share, new_size, share_server=None):
204 """Extend a sub-directory/share in the GlusterFS volume."""
205 self._set_directory_quota(share, new_size)
207 def shrink_share(self, share, new_size, share_server=None):
208 """Shrink a sub-directory/share in the GlusterFS volume."""
209 usage = self._get_directory_usage(share)
210 consumed_limit = int(math.ceil(usage))
211 if consumed_limit > new_size:
212 raise exception.ShareShrinkingPossibleDataLoss(
213 share_id=share['id'])
215 self._set_directory_quota(share, new_size)
217 def _set_directory_quota(self, share, new_size):
218 sizestr = str(new_size) + 'GB'
219 share_dir = '/' + share['name']
221 args = ('volume', 'quota', self.gluster_manager.volume,
222 'limit-usage', share_dir, sizestr)
224 try:
225 self.gluster_manager.gluster_call(*args)
226 except exception.GlusterfsException:
227 LOG.error('Unable to set quota share %s', share['name'])
228 raise
230 def _get_directory_usage(self, share):
231 share_dir = '/' + share['name']
233 args = ('--xml', 'volume', 'quota', self.gluster_manager.volume,
234 'list', share_dir)
236 try:
237 out, err = self.gluster_manager.gluster_call(*args)
238 except exception.GlusterfsException:
239 LOG.error('Unable to get quota share %s', share['name'])
240 raise
242 volxml = etree.fromstring(out)
243 usage_byte = volxml.find('./volQuota/limit/used_space').text
244 usage = utils.translate_string_size_to_float(usage_byte)
246 return usage