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

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. 

15 

16"""Flat network GlusterFS Driver. 

17 

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. 

23 

24TODO(rraja): support SMB protocol. 

25""" 

26 

27import re 

28import socket 

29import sys 

30 

31from oslo_config import cfg 

32from oslo_log import log 

33 

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 

42 

43 

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] 

60 

61CONF = cfg.CONF 

62CONF.register_opts(GlusterfsManilaShare_opts) 

63 

64LOG = log.getLogger(__name__) 

65 

66 

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' 

71 

72 

73class GlusterfsShareDriver(driver.ExecuteMixin, driver.GaneshaMixin, 

74 layout.GlusterfsShareDriverBase): 

75 """Execute commands relating to Shares.""" 

76 

77 GLUSTERFS_VERSION_MIN = (3, 5) 

78 

79 supported_layouts = ('layout_directory.GlusterfsDirectoryMappedLayout', 

80 'layout_volume.GlusterfsVolumeMappedLayout') 

81 supported_protocols = ('NFS',) 

82 

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') 

94 

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) 

99 

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']) 

120 

121 def check_for_setup_error(self): 

122 pass 

123 

124 def _update_share_stats(self): 

125 """Retrieve stats info from the GlusterFS volume.""" 

126 

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) 

139 

140 def get_network_allocations_number(self): 

141 return 0 

142 

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 

153 

154 @property 

155 def supported_access_types(self): 

156 return self.nfs_helper.supported_access_types 

157 

158 @property 

159 def supported_access_levels(self): 

160 return self.nfs_helper.supported_access_levels 

161 

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) 

168 

169 

170class GlusterNFSHelper(ganesha.NASHelperBase): 

171 """Manage shares with Gluster-NFS server.""" 

172 

173 supported_access_types = ('ip', ) 

174 supported_access_levels = (constants.ACCESS_LEVEL_RW, ) 

175 

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) 

180 

181 def get_export(self, share): 

182 return self.gluster_manager.export 

183 

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 

202 

203 def update_access(self, base_path, share, add_rules, delete_rules, 

204 recovery=False): 

205 """Update access rules.""" 

206 

207 existing_rules_set = set() 

208 

209 # The name of the directory, which is exported as the share. 

210 export_dir = self.gluster_manager.path[1:] 

211 

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() 

216 

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) 

223 

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) 

230 

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) 

238 

239 

240class GlusterNFSVolHelper(GlusterNFSHelper): 

241 """Manage shares with Gluster-NFS server, volume mapped variant.""" 

242 

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 [] 

247 

248 def update_access(self, base_path, share, add_rules, delete_rules, 

249 recovery=False): 

250 """Update access rules.""" 

251 

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) 

257 

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, '*')) 

264 

265 for args in argseq: 

266 self.gluster_manager.set_vol_option(*args) 

267 

268 

269class GaneshaNFSHelper(ganesha.GaneshaNASHelper): 

270 

271 shared_data = {} 

272 

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) 

289 

290 def get_export(self, share): 

291 return ':/'.join((self.ganesha_host, share['name'] + "--<access-id>")) 

292 

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 

303 

304 if _init_helper(): 

305 tagdata = self.shared_data[self.tag] 

306 self.ganesha = tagdata['ganesha'] 

307 self.export_template = tagdata['export_template'] 

308 

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 

315 

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} 

321 

322 def update_access(self, base_path, share, add_rules, delete_rules, 

323 recovery=False): 

324 """Update access rules.""" 

325 

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)