Coverage for manila/share/drivers/glusterfs/layout_volume.py: 98%

294 statements  

« 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. 

15 

16"""GlusterFS volume mapped share layout.""" 

17 

18import os 

19import random 

20import re 

21import shutil 

22import string 

23import tempfile 

24 

25from defusedxml import ElementTree as etree 

26from oslo_config import cfg 

27from oslo_log import log 

28 

29from manila import exception 

30from manila.i18n import _ 

31from manila.privsep import os as privsep_os 

32from manila.share.drivers.glusterfs import common 

33from manila.share.drivers.glusterfs import layout 

34from manila import utils 

35 

36LOG = log.getLogger(__name__) 

37 

38 

39glusterfs_volume_mapped_opts = [ 

40 cfg.ListOpt('glusterfs_servers', 

41 default=[], 

42 help='List of GlusterFS servers that can be used to create ' 

43 'shares. Each GlusterFS server should be of the form ' 

44 '[remoteuser@]<volserver>, and they are assumed to ' 

45 'belong to distinct Gluster clusters.'), 

46 cfg.StrOpt('glusterfs_volume_pattern', 

47 help='Regular expression template used to filter ' 

48 'GlusterFS volumes for share creation. ' 

49 'The regex template can optionally (ie. with support ' 

50 'of the GlusterFS backend) contain the #{size} ' 

51 'parameter which matches an integer (sequence of ' 

52 'digits) in which case the value shall be interpreted as ' 

53 'size of the volume in GB. Examples: ' 

54 r'"manila-share-volume-\d+$", ' 

55 r'"manila-share-volume-#{size}G-\d+$"; ' 

56 'with matching volume names, respectively: ' 

57 '"manila-share-volume-12", "manila-share-volume-3G-13". ' 

58 'In latter example, the number that matches "#{size}", ' 

59 'that is, 3, is an indication that the size of volume ' 

60 'is 3G.'), 

61] 

62 

63 

64CONF = cfg.CONF 

65CONF.register_opts(glusterfs_volume_mapped_opts) 

66 

67# The dict specifying named parameters 

68# that can be used with glusterfs_volume_pattern 

69# in #{<param>} format. 

70# For each of them we give regex pattern it matches 

71# and a transformer function ('trans') for the matched 

72# string value. 

73# Currently we handle only #{size}. 

74PATTERN_DICT = {'size': {'pattern': r'(?P<size>\d+)', 'trans': int}} 

75USER_MANILA_SHARE = 'user.manila-share' 

76USER_CLONED_FROM = 'user.manila-cloned-from' 

77UUID_RE = re.compile(r'\A[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\Z', re.I) 

78 

79 

80class GlusterfsVolumeMappedLayout(layout.GlusterfsShareLayoutBase): 

81 

82 _snapshots_are_supported = True 

83 

84 def __init__(self, driver, *args, **kwargs): 

85 super(GlusterfsVolumeMappedLayout, self).__init__( 

86 driver, *args, **kwargs) 

87 self.gluster_used_vols = set() 

88 self.configuration.append_config_values( 

89 common.glusterfs_common_opts) 

90 self.configuration.append_config_values( 

91 glusterfs_volume_mapped_opts) 

92 self.gluster_nosnap_vols_dict = {} 

93 self.volume_pattern = self._compile_volume_pattern() 

94 self.volume_pattern_keys = self.volume_pattern.groupindex.keys() 

95 for srvaddr in self.configuration.glusterfs_servers: 

96 # format check for srvaddr 

97 self._glustermanager(srvaddr, False) 

98 self.glusterfs_versions = {} 

99 self.private_storage = kwargs.get('private_storage') 

100 

101 def _compile_volume_pattern(self): 

102 """Compile a RegexObject from the config specified regex template. 

103 

104 (cfg.glusterfs_volume_pattern) 

105 """ 

106 

107 subdict = {} 

108 for key, val in PATTERN_DICT.items(): 

109 subdict[key] = val['pattern'] 

110 

111 # Using templates with placeholder syntax #{<var>} 

112 class CustomTemplate(string.Template): 

113 delimiter = '#' 

114 

115 volume_pattern = CustomTemplate( 

116 self.configuration.glusterfs_volume_pattern).substitute( 

117 subdict) 

118 return re.compile(volume_pattern) 

119 

120 def do_setup(self, context): 

121 """Setup the GlusterFS volumes.""" 

122 glusterfs_versions, exceptions = {}, {} 

123 for srvaddr in self.configuration.glusterfs_servers: 

124 try: 

125 glusterfs_versions[srvaddr] = self._glustermanager( 

126 srvaddr, False).get_gluster_version() 

127 except exception.GlusterfsException as exc: 

128 exceptions[srvaddr] = str(exc) 

129 if exceptions: 

130 for srvaddr, excmsg in exceptions.items(): 

131 LOG.error("'gluster version' failed on server " 

132 "%(server)s with: %(message)s", 

133 {'server': srvaddr, 'message': excmsg}) 

134 raise exception.GlusterfsException(_( 

135 "'gluster version' failed on servers %s") % ( 

136 ','.join(exceptions.keys()))) 

137 notsupp_servers = [] 

138 for srvaddr, vers in glusterfs_versions.items(): 

139 if common.numreduct(vers) < self.driver.GLUSTERFS_VERSION_MIN: 

140 notsupp_servers.append(srvaddr) 

141 if notsupp_servers: 

142 gluster_version_min_str = '.'.join( 

143 str(c) for c in self.driver.GLUSTERFS_VERSION_MIN) 

144 for srvaddr in notsupp_servers: 

145 LOG.error("GlusterFS version %(version)s on server " 

146 "%(server)s is not supported, " 

147 "minimum requirement: %(minvers)s", 

148 {'server': srvaddr, 

149 'version': '.'.join(glusterfs_versions[srvaddr]), 

150 'minvers': gluster_version_min_str}) 

151 raise exception.GlusterfsException(_( 

152 "Unsupported GlusterFS version on servers %(servers)s, " 

153 "minimum requirement: %(minvers)s") % { 

154 'servers': ','.join(notsupp_servers), 

155 'minvers': gluster_version_min_str}) 

156 self.glusterfs_versions = glusterfs_versions 

157 

158 gluster_volumes_initial = set( 

159 self._fetch_gluster_volumes(filter_used=False)) 

160 if not gluster_volumes_initial: 

161 # No suitable volumes are found on the Gluster end. 

162 # Raise exception. 

163 msg = (_("Gluster backend does not provide any volume " 

164 "matching pattern %s" 

165 ) % self.configuration.glusterfs_volume_pattern) 

166 LOG.error(msg) 

167 raise exception.GlusterfsException(msg) 

168 

169 LOG.info("Found %d Gluster volumes allocated for Manila.", 

170 len(gluster_volumes_initial)) 

171 

172 self._check_mount_glusterfs() 

173 

174 def _glustermanager(self, gluster_address, req_volume=True): 

175 """Create GlusterManager object for gluster_address.""" 

176 

177 return common.GlusterManager( 

178 gluster_address, self.driver._execute, 

179 self.configuration.glusterfs_path_to_private_key, 

180 self.configuration.glusterfs_server_password, 

181 requires={'volume': req_volume}) 

182 

183 def _share_manager(self, share): 

184 """Return GlusterManager object representing share's backend.""" 

185 gluster_address = self.private_storage.get(share['id'], 'volume') 

186 if gluster_address is None: 

187 return 

188 return self._glustermanager(gluster_address) 

189 

190 def _fetch_gluster_volumes(self, filter_used=True): 

191 """Do a 'gluster volume list | grep <volume pattern>'. 

192 

193 Aggregate the results from all servers. 

194 Extract the named groups from the matching volume names 

195 using the specs given in PATTERN_DICT. 

196 Return a dict with keys of the form <server>:/<volname> 

197 and values being dicts that map names of named groups 

198 to their extracted value. 

199 """ 

200 

201 volumes_dict = {} 

202 for srvaddr in self.configuration.glusterfs_servers: 

203 gluster_mgr = self._glustermanager(srvaddr, False) 

204 if gluster_mgr.user: 204 ↛ 208line 204 didn't jump to line 208 because the condition on line 204 was always true

205 logmsg = ("Retrieving volume list " 

206 "on host %s") % gluster_mgr.host 

207 else: 

208 logmsg = ("Retrieving volume list") 

209 out, err = gluster_mgr.gluster_call('volume', 'list', log=logmsg) 

210 for volname in out.split("\n"): 

211 patmatch = self.volume_pattern.match(volname) 

212 if not patmatch: 

213 continue 

214 comp_vol = gluster_mgr.components.copy() 

215 comp_vol.update({'volume': volname}) 

216 gluster_mgr_vol = self._glustermanager(comp_vol) 

217 if filter_used: 

218 vshr = gluster_mgr_vol.get_vol_option( 

219 USER_MANILA_SHARE) or '' 

220 if UUID_RE.search(vshr): 

221 continue 

222 pattern_dict = {} 

223 for key in self.volume_pattern_keys: 

224 keymatch = patmatch.group(key) 

225 if keymatch is None: 

226 pattern_dict[key] = None 

227 else: 

228 trans = PATTERN_DICT[key].get('trans', lambda x: x) 

229 pattern_dict[key] = trans(keymatch) 

230 volumes_dict[gluster_mgr_vol.qualified] = pattern_dict 

231 return volumes_dict 

232 

233 @utils.synchronized("glusterfs_native", external=False) 

234 def _pop_gluster_vol(self, size=None): 

235 """Pick an unbound volume. 

236 

237 Do a _fetch_gluster_volumes() first to get the complete 

238 list of usable volumes. 

239 Keep only the unbound ones (ones that are not yet used to 

240 back a share). 

241 If size is given, try to pick one which has a size specification 

242 (according to the 'size' named group of the volume pattern), 

243 and its size is greater-than-or-equal to the given size. 

244 Return the volume chosen (in <host>:/<volname> format). 

245 """ 

246 

247 voldict = self._fetch_gluster_volumes() 

248 # calculate the set of unused volumes 

249 unused_vols = set(voldict) - self.gluster_used_vols 

250 

251 if not unused_vols: 

252 # No volumes available for use as share. Warn user. 

253 LOG.warning("No unused gluster volumes available for use as " 

254 "share! Create share won't be supported unless " 

255 "existing shares are deleted or some gluster " 

256 "volumes are created with names matching " 

257 "'glusterfs_volume_pattern'.") 

258 else: 

259 LOG.info("Number of gluster volumes in use: " 

260 "%(inuse-numvols)s. Number of gluster volumes " 

261 "available for use as share: %(unused-numvols)s", 

262 {'inuse-numvols': len(self.gluster_used_vols), 

263 'unused-numvols': len(unused_vols)}) 

264 

265 # volmap is the data structure used to categorize and sort 

266 # the unused volumes. It's a nested dictionary of structure 

267 # {<size>: <hostmap>} 

268 # where <size> is either an integer or None, 

269 # <hostmap> is a dictionary of structure {<host>: <vols>} 

270 # where <host> is a host name (IP address), <vols> is a list 

271 # of volumes (gluster addresses). 

272 volmap = {None: {}} 

273 # if both caller has specified size and 'size' occurs as 

274 # a parameter in the volume pattern... 

275 if size and 'size' in self.volume_pattern_keys: 

276 # then this function is used to extract the 

277 # size value for a given volume from the voldict... 

278 get_volsize = lambda vol: voldict[vol]['size'] # noqa: E731 

279 else: 

280 # else just use a stub. 

281 get_volsize = lambda vol: None # noqa: E731 

282 for vol in unused_vols: 

283 # For each unused volume, we extract the <size> 

284 # and <host> values with which it can be inserted 

285 # into the volmap, and conditionally perform 

286 # the insertion (with the condition being: once 

287 # caller specified size and a size indication was 

288 # found in the volume name, we require that the 

289 # indicated size adheres to caller's spec). 

290 volsize = get_volsize(vol) 

291 if not volsize or volsize >= size: 

292 hostmap = volmap.get(volsize) 

293 if not hostmap: 293 ↛ 296line 293 didn't jump to line 296 because the condition on line 293 was always true

294 hostmap = {} 

295 volmap[volsize] = hostmap 

296 host = self._glustermanager(vol).host 

297 hostvols = hostmap.get(host) 

298 if not hostvols: 298 ↛ 301line 298 didn't jump to line 301 because the condition on line 298 was always true

299 hostvols = [] 

300 hostmap[host] = hostvols 

301 hostvols.append(vol) 

302 if len(volmap) > 1: 

303 # volmap has keys apart from the default None, 

304 # ie. volumes with sensible and adherent size 

305 # indication have been found. Then pick the smallest 

306 # of the size values. 

307 chosen_size = sorted(n for n in volmap.keys() if n)[0] 

308 else: 

309 chosen_size = None 

310 chosen_hostmap = volmap[chosen_size] 

311 if not chosen_hostmap: 

312 msg = (_("Couldn't find a free gluster volume to use.")) 

313 LOG.error(msg) 

314 raise exception.GlusterfsException(msg) 

315 

316 # From the hosts we choose randomly to tend towards 

317 # even distribution of share backing volumes among 

318 # Gluster clusters. 

319 chosen_host = random.choice(list(chosen_hostmap.keys())) 

320 # Within a host's volumes, choose alphabetically first, 

321 # to make it predictable. 

322 vol = sorted(chosen_hostmap[chosen_host])[0] 

323 self.gluster_used_vols.add(vol) 

324 return vol 

325 

326 @utils.synchronized("glusterfs_native", external=False) 

327 def _push_gluster_vol(self, exp_locn): 

328 try: 

329 self.gluster_used_vols.remove(exp_locn) 

330 except KeyError: 

331 msg = (_("Couldn't find the share in used list.")) 

332 LOG.error(msg) 

333 raise exception.GlusterfsException(msg) 

334 

335 def _wipe_gluster_vol(self, gluster_mgr): 

336 

337 # Create a temporary mount. 

338 gluster_export = gluster_mgr.export 

339 tmpdir = tempfile.mkdtemp() 

340 try: 

341 common._mount_gluster_vol(self.driver._execute, gluster_export, 

342 tmpdir) 

343 except exception.GlusterfsException: 

344 shutil.rmtree(tmpdir, ignore_errors=True) 

345 raise 

346 

347 # Delete the contents of a GlusterFS volume that is temporarily 

348 # mounted. 

349 # From GlusterFS version 3.7, two directories, '.trashcan' at the root 

350 # of the GlusterFS volume and 'internal_op' within the '.trashcan' 

351 # directory, are internally created when a GlusterFS volume is started. 

352 # GlusterFS does not allow unlink(2) of the two directories. So do not 

353 # delete the paths of the two directories, but delete their contents 

354 # along with the rest of the contents of the volume. 

355 srvaddr = gluster_mgr.host_access 

356 ignored_dirs = [] 

357 if common.numreduct(self.glusterfs_versions[srvaddr]) > (3, 6): 

358 ignored_dirs = map(lambda x: os.path.join(tmpdir, *x), 

359 [('.trashcan', ), ('.trashcan', 'internal_op')]) 

360 ignored_dirs = list(ignored_dirs) 

361 ignored_dirs = [ignored_dirs[0], ignored_dirs[1]] 

362 

363 try: 

364 privsep_os.find( 

365 tmpdir, dirs_to_ignore=ignored_dirs, delete=True) 

366 except exception.ProcessExecutionError as exc: 

367 msg = (_("Error trying to wipe gluster volume. " 

368 "gluster_export: %(export)s, Error: %(error)s") % 

369 {'export': gluster_export, 'error': exc.stderr}) 

370 LOG.error(msg) 

371 raise exception.GlusterfsException(msg) 

372 finally: 

373 # Unmount. 

374 common._umount_gluster_vol(tmpdir) 

375 shutil.rmtree(tmpdir, ignore_errors=True) 

376 

377 def create_share(self, context, share, share_server=None): 

378 """Create a share using GlusterFS volume. 

379 

380 1 Manila share = 1 GlusterFS volume. Pick an unused 

381 GlusterFS volume for use as a share. 

382 """ 

383 try: 

384 vol = self._pop_gluster_vol(share['size']) 

385 except exception.GlusterfsException: 

386 msg = ("Error creating share %(share_id)s", 

387 {'share_id': share['id']}) 

388 LOG.error(msg) 

389 raise 

390 

391 gmgr = self._glustermanager(vol) 

392 export = self.driver._setup_via_manager( 

393 {'share': share, 'manager': gmgr}) 

394 

395 gmgr.set_vol_option(USER_MANILA_SHARE, share['id']) 

396 self.private_storage.update(share['id'], {'volume': vol}) 

397 

398 # TODO(deepakcs): Enable quota and set it to the share size. 

399 

400 # For native protocol, the export_location should be of the form: 

401 # server:/volname 

402 LOG.info("export_location sent back from create_share: %s", 

403 export) 

404 return export 

405 

406 def delete_share(self, context, share, share_server=None): 

407 """Delete a share on the GlusterFS volume. 

408 

409 1 Manila share = 1 GlusterFS volume. Put the gluster 

410 volume back in the available list. 

411 """ 

412 gmgr = self._share_manager(share) 

413 if not gmgr: 

414 # Share does not have a record in private storage. 

415 # It means create_share{,_from_snapshot} did not 

416 # succeed(*). In that case we should not obstruct 

417 # share deletion, so we just return doing nothing. 

418 # 

419 # (*) or we have a database corruption but then 

420 # basically does not matter what we do here 

421 return 

422 clone_of = gmgr.get_vol_option(USER_CLONED_FROM) or '' 

423 try: 

424 if UUID_RE.search(clone_of): 

425 # We take responsibility for the lifecycle 

426 # management of those volumes which were 

427 # created by us (as snapshot clones) ... 

428 gmgr.gluster_call('volume', 'delete', gmgr.volume) 

429 else: 

430 # ... for volumes that come from the pool, we return 

431 # them to the pool (after some purification rituals) 

432 self._wipe_gluster_vol(gmgr) 

433 gmgr.set_vol_option(USER_MANILA_SHARE, 'NONE') 

434 gmgr.set_vol_option('nfs.disable', 'on') 

435 

436 # When deleting the share instance, we may need to 

437 # update'self.gluster_used_vols' again 

438 self.gluster_used_vols.add(gmgr.qualified) 

439 self._push_gluster_vol(gmgr.qualified) 

440 except exception.GlusterfsException: 

441 msg = ("Error during delete_share request for " 

442 "share %(share_id)s", {'share_id': share['id']}) 

443 LOG.error(msg) 

444 raise 

445 

446 self.private_storage.delete(share['id']) 

447 # TODO(deepakcs): Disable quota. 

448 

449 @staticmethod 

450 def _find_actual_backend_snapshot_name(gluster_mgr, snapshot): 

451 args = ('snapshot', 'list', gluster_mgr.volume, '--mode=script') 

452 out, err = gluster_mgr.gluster_call( 

453 *args, 

454 log=("Retrieving snapshot list")) 

455 snapgrep = list(filter(lambda x: snapshot['id'] in x, out.split("\n"))) 

456 if len(snapgrep) != 1: 

457 msg = (_("Failed to identify backing GlusterFS object " 

458 "for snapshot %(snap_id)s of share %(share_id)s: " 

459 "a single candidate was expected, %(found)d was found.") % 

460 {'snap_id': snapshot['id'], 

461 'share_id': snapshot['share_id'], 

462 'found': len(snapgrep)}) 

463 raise exception.GlusterfsException(msg) 

464 backend_snapshot_name = snapgrep[0] 

465 return backend_snapshot_name 

466 

467 def create_share_from_snapshot(self, context, share, snapshot, 

468 share_server=None, parent_share=None): 

469 old_gmgr = self._share_manager(snapshot['share_instance']) 

470 

471 # Snapshot clone feature in GlusterFS server essential to support this 

472 # API is available in GlusterFS server versions 3.7 and higher. So do 

473 # a version check. 

474 vers = self.glusterfs_versions[old_gmgr.host_access] 

475 minvers = (3, 7) 

476 if common.numreduct(vers) < minvers: 

477 minvers_str = '.'.join(str(c) for c in minvers) 

478 vers_str = '.'.join(vers) 

479 msg = (_("GlusterFS version %(version)s on server %(server)s does " 

480 "not support creation of shares from snapshot. " 

481 "minimum requirement: %(minversion)s") % 

482 {'version': vers_str, 'server': old_gmgr.host, 

483 'minversion': minvers_str}) 

484 LOG.error(msg) 

485 raise exception.GlusterfsException(msg) 

486 

487 # Clone the snapshot. The snapshot clone, a new GlusterFS volume 

488 # would serve as a share. 

489 backend_snapshot_name = self._find_actual_backend_snapshot_name( 

490 old_gmgr, snapshot) 

491 volume = ''.join(['manila-', share['id']]) 

492 

493 # Query the status of the snapshot, if it is Started, the activate 

494 # step will be skipped 

495 args = ('snapshot', 'info', backend_snapshot_name) 

496 out, err = old_gmgr.gluster_call( 

497 *args, 

498 log=("Query the status of the snapshot")) 

499 

500 gfs_snapshot_state = "" 

501 for gfs_snapshot_info in out.split('\t'): 

502 gfs_snapshot_states = re.search(r'Started', gfs_snapshot_info, 

503 re.I) 

504 if gfs_snapshot_states: 504 ↛ 505line 504 didn't jump to line 505 because the condition on line 504 was never true

505 gfs_snapshot_state = "Started" 

506 

507 if gfs_snapshot_state == "Started": 507 ↛ 508line 507 didn't jump to line 508 because the condition on line 507 was never true

508 args_tuple = (('snapshot', 'clone', 

509 volume, backend_snapshot_name), 

510 ('volume', 'start', volume)) 

511 else: 

512 args_tuple = (('snapshot', 'activate', backend_snapshot_name, 

513 'force', '--mode=script'), 

514 ('snapshot', 'clone', volume, 

515 backend_snapshot_name), 

516 ('volume', 'start', volume)) 

517 

518 for args in args_tuple: 

519 out, err = old_gmgr.gluster_call( 

520 *args, 

521 log=("Creating share from snapshot")) 

522 

523 # Get a manager for the new volume/share. 

524 comp_vol = old_gmgr.components.copy() 

525 comp_vol.update({'volume': volume}) 

526 gmgr = self._glustermanager(comp_vol) 

527 export = self.driver._setup_via_manager( 

528 {'share': share, 'manager': gmgr}, 

529 {'share': snapshot['share_instance'], 'manager': old_gmgr}) 

530 

531 export = [export, ] 

532 argseq = (('set', 

533 [USER_CLONED_FROM, snapshot['share_id']]), 

534 ('set', [USER_MANILA_SHARE, share['id']])) 

535 for op, opargs in argseq: 

536 args = ['volume', op, gmgr.volume] + opargs 

537 gmgr.gluster_call(*args, log=("Creating share from snapshot")) 

538 

539 self.gluster_used_vols.add(gmgr.qualified) 

540 self.private_storage.update(share['id'], {'volume': gmgr.qualified}) 

541 

542 return export 

543 

544 def create_snapshot(self, context, snapshot, share_server=None): 

545 """Creates a snapshot.""" 

546 

547 gluster_mgr = self._share_manager(snapshot['share']) 

548 if gluster_mgr.qualified in self.gluster_nosnap_vols_dict: 

549 opret, operrno = -1, 0 

550 operrstr = self.gluster_nosnap_vols_dict[gluster_mgr.qualified] 

551 else: 

552 args = ('--xml', 'snapshot', 'create', 'manila-' + snapshot['id'], 

553 gluster_mgr.volume) 

554 out, err = gluster_mgr.gluster_call( 

555 *args, 

556 log=("Retrieving volume info")) 

557 

558 if not out: 

559 raise exception.GlusterfsException( 

560 'gluster volume info %s: no data received' % 

561 gluster_mgr.volume 

562 ) 

563 

564 outxml = etree.fromstring(out) 

565 opret = int(common.volxml_get(outxml, 'opRet')) 

566 operrno = int(common.volxml_get(outxml, 'opErrno')) 

567 operrstr = common.volxml_get(outxml, 'opErrstr', default=None) 

568 

569 if opret == -1: 

570 vers = self.glusterfs_versions[gluster_mgr.host_access] 

571 if common.numreduct(vers) > (3, 6): 

572 # This logic has not yet been implemented in GlusterFS 3.6 

573 if operrno == 0: 573 ↛ 581line 573 didn't jump to line 581 because the condition on line 573 was always true

574 self.gluster_nosnap_vols_dict[ 

575 gluster_mgr.qualified] = operrstr 

576 msg = _("Share %(share_id)s does not support snapshots: " 

577 "%(errstr)s.") % {'share_id': snapshot['share_id'], 

578 'errstr': operrstr} 

579 LOG.error(msg) 

580 raise exception.ShareSnapshotNotSupported(msg) 

581 raise exception.GlusterfsException( 

582 _("Creating snapshot for share %(share_id)s failed " 

583 "with %(errno)d: %(errstr)s") % { 

584 'share_id': snapshot['share_id'], 

585 'errno': operrno, 

586 'errstr': operrstr}) 

587 

588 def delete_snapshot(self, context, snapshot, share_server=None): 

589 """Deletes a snapshot.""" 

590 

591 gluster_mgr = self._share_manager(snapshot['share']) 

592 backend_snapshot_name = self._find_actual_backend_snapshot_name( 

593 gluster_mgr, snapshot) 

594 args = ('--xml', 'snapshot', 'delete', backend_snapshot_name, 

595 '--mode=script') 

596 out, err = gluster_mgr.gluster_call( 

597 *args, 

598 log=("Error deleting snapshot")) 

599 

600 if not out: 

601 raise exception.GlusterfsException( 

602 _('gluster snapshot delete %s: no data received') % 

603 gluster_mgr.volume 

604 ) 

605 

606 outxml = etree.fromstring(out) 

607 gluster_mgr.xml_response_check(outxml, args[1:]) 

608 

609 def ensure_share(self, context, share, share_server=None): 

610 """Invoked to ensure that share is exported.""" 

611 gmgr = self._share_manager(share) 

612 self.gluster_used_vols.add(gmgr.qualified) 

613 

614 gmgr.set_vol_option(USER_MANILA_SHARE, share['id']) 

615 

616 # Debt... 

617 

618 def manage_existing(self, share, driver_options): 

619 raise NotImplementedError() 

620 

621 def unmanage(self, share): 

622 raise NotImplementedError() 

623 

624 def extend_share(self, share, new_size, share_server=None): 

625 raise NotImplementedError() 

626 

627 def shrink_share(self, share, new_size, share_server=None): 

628 raise NotImplementedError()