Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

5# This program is free software; you can redistribute it and/or modify 

6# it under the terms of the GNU Lesser General Public License as published 

7# by the Free Software Foundation; version 2.1 only. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU Lesser General Public License for more details. 

13# 

14# You should have received a copy of the GNU Lesser General Public License 

15# along with this program; if not, write to the Free Software Foundation, Inc., 

16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 

17# 

18# FileSR: local-file storage repository 

19 

20from sm_typing import Dict, Optional, List, override 

21 

22import SR 

23import VDI 

24import SRCommand 

25import contextlib 

26import util 

27import scsiutil 

28import lock 

29import os 

30import errno 

31import xs_errors 

32import cleanup 

33import blktap2 

34import time 

35import glob 

36from uuid import uuid4 

37from cowutil import getCowUtil, getImageStringFromVdiType, getVdiTypeFromImageFormat 

38from vditype import VdiType, VdiTypeExtension, VDI_COW_TYPES, VDI_TYPE_TO_EXTENSION 

39import xmlrpc.client 

40import XenAPI # pylint: disable=import-error 

41from constants import CBTLOG_TAG 

42 

43geneology: Dict[str, List[str]] = {} 

44CAPABILITIES = ["SR_PROBE", "SR_UPDATE", \ 

45 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", \ 

46 "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR", 

47 "VDI_GENERATE_CONFIG", "ATOMIC_PAUSE", "VDI_CONFIG_CBT", 

48 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING"] 

49 

50CONFIGURATION = [ 

51 ['location', 'local directory path (required)'], 

52 ['preferred-image-formats', 'list of preferred image formats to use (default: VHD,QCOW2)'] 

53] 

54 

55DRIVER_INFO = { 

56 'name': 'Local Path VHD and QCOW2', 

57 'description': 'SR plugin which represents disks as VHD and QCOW2 files stored on a local path', 

58 'vendor': 'Citrix Systems Inc', 

59 'copyright': '(C) 2008 Citrix Systems Inc', 

60 'driver_version': '1.0', 

61 'required_api_version': '1.0', 

62 'capabilities': CAPABILITIES, 

63 'configuration': CONFIGURATION 

64 } 

65 

66JOURNAL_FILE_PREFIX = ".journal-" 

67 

68OPS_EXCLUSIVE = [ 

69 "sr_create", "sr_delete", "sr_probe", "sr_attach", "sr_detach", 

70 "sr_scan", "vdi_init", "vdi_create", "vdi_delete", "vdi_attach", 

71 "vdi_detach", "vdi_resize_online", "vdi_snapshot", "vdi_clone"] 

72 

73DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

74 

75 

76class FileSR(SR.SR): 

77 """Local file storage repository""" 

78 

79 SR_TYPE = "file" 

80 

81 @override 

82 @staticmethod 

83 def handles(srtype) -> bool: 

84 return srtype == 'file' 

85 

86 def _check_o_direct(self): 

87 if self.sr_ref and self.session is not None: 

88 other_config = self.session.xenapi.SR.get_other_config(self.sr_ref) 

89 o_direct = other_config.get("o_direct") 

90 self.o_direct = o_direct is not None and o_direct == "true" 

91 else: 

92 self.o_direct = True 

93 

94 def __init__(self, srcmd, sr_uuid): 

95 # We call SR.SR.__init__ explicitly because 

96 # "super" sometimes failed due to circular imports 

97 SR.SR.__init__(self, srcmd, sr_uuid) 

98 self.image_info = {} 

99 self._init_image_formats() 

100 self._check_o_direct() 

101 

102 @override 

103 def load(self, sr_uuid) -> None: 

104 self.ops_exclusive = OPS_EXCLUSIVE 

105 self.lock = lock.Lock(lock.LOCK_TYPE_SR, self.uuid) 

106 self.sr_vditype = SR.DEFAULT_TAP 

107 if 'location' not in self.dconf or not self.dconf['location']: 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true

108 raise xs_errors.XenError('ConfigLocationMissing') 

109 self.remotepath = self.dconf['location'] 

110 self.path = os.path.join(SR.MOUNT_BASE, sr_uuid) 

111 self.linkpath = self.path 

112 self.mountpoint = self.path 

113 self.attached = False 

114 self.driver_config = DRIVER_CONFIG 

115 

116 @override 

117 def create(self, sr_uuid, size) -> None: 

118 """ Create the SR. The path must not already exist, or if it does, 

119 it must be empty. (This accounts for the case where the user has 

120 mounted a device onto a directory manually and want to use this as the 

121 root of a file-based SR.) """ 

122 try: 

123 if util.ioretry(lambda: util.pathexists(self.remotepath)): 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true

124 if len(util.ioretry(lambda: util.listdir(self.remotepath))) != 0: 

125 raise xs_errors.XenError('SRExists') 

126 else: 

127 try: 

128 util.ioretry(lambda: os.mkdir(self.remotepath)) 

129 except util.CommandException as inst: 

130 if inst.code == errno.EEXIST: 

131 raise xs_errors.XenError('SRExists') 

132 else: 

133 raise xs_errors.XenError('FileSRCreate', \ 

134 opterr='directory creation failure %d' \ 

135 % inst.code) 

136 except: 

137 raise xs_errors.XenError('FileSRCreate') 

138 

139 @override 

140 def delete(self, sr_uuid) -> None: 

141 self.attach(sr_uuid) 

142 cleanup.gc_force(self.session, self.uuid) 

143 

144 # check to make sure no VDIs are present; then remove old 

145 # files that are non VDI's 

146 try: 

147 if util.ioretry(lambda: util.pathexists(self.path)): 

148 #Load the VDI list 

149 self._loadvdis() 

150 for uuid in self.vdis: 

151 if not self.vdis[uuid].deleted: 

152 raise xs_errors.XenError('SRNotEmpty', \ 

153 opterr='VDIs still exist in SR') 

154 

155 # remove everything else, there are no vdi's 

156 for name in util.ioretry(lambda: util.listdir(self.path)): 

157 fullpath = os.path.join(self.path, name) 

158 try: 

159 util.ioretry(lambda: os.unlink(fullpath)) 

160 except util.CommandException as inst: 

161 if inst.code != errno.ENOENT and \ 

162 inst.code != errno.EISDIR: 

163 raise xs_errors.XenError('FileSRDelete', \ 

164 opterr='failed to remove %s error %d' \ 

165 % (fullpath, inst.code)) 

166 self.detach(sr_uuid) 

167 except util.CommandException as inst: 

168 self.detach(sr_uuid) 

169 raise xs_errors.XenError('FileSRDelete', \ 

170 opterr='error %d' % inst.code) 

171 

172 @override 

173 def attach(self, sr_uuid) -> None: 

174 self.attach_and_bind(sr_uuid) 

175 

176 def attach_and_bind(self, sr_uuid, bind=True) -> None: 

177 if not self._checkmount(): 

178 try: 

179 util.ioretry(lambda: util.makedirs(self.path, mode=0o700)) 

180 except util.CommandException as inst: 

181 if inst.code != errno.EEXIST: 

182 raise xs_errors.XenError("FileSRCreate", \ 

183 opterr='fail to create mount point. Errno is %s' % inst.code) 

184 try: 

185 cmd = ["mount", self.remotepath, self.path] 

186 if bind: 

187 cmd.append("--bind") 

188 util.pread(cmd) 

189 os.chmod(self.path, mode=0o0700) 

190 except util.CommandException as inst: 

191 raise xs_errors.XenError('FileSRCreate', \ 

192 opterr='fail to mount FileSR. Errno is %s' % inst.code) 

193 self.attached = True 

194 

195 @override 

196 def detach(self, sr_uuid) -> None: 

197 if self._checkmount(): 

198 try: 

199 util.SMlog("Aborting GC/coalesce") 

200 cleanup.abort(self.uuid) 

201 os.chdir(SR.MOUNT_BASE) 

202 util.pread(["umount", self.path]) 

203 os.rmdir(self.path) 

204 except Exception as e: 

205 raise xs_errors.XenError('SRInUse', opterr=str(e)) 

206 self.attached = False 

207 

208 @override 

209 def scan(self, sr_uuid) -> None: 

210 if not self._checkmount(): 

211 raise xs_errors.XenError('SRUnavailable', \ 

212 opterr='no such directory %s' % self.path) 

213 

214 if not self.vdis: 214 ↛ 217line 214 didn't jump to line 217, because the condition on line 214 was never false

215 self._loadvdis() 

216 

217 if not self.passthrough: 

218 self.physical_size = self._getsize() 

219 self.physical_utilisation = self._getutilisation() 

220 

221 for uuid in list(self.vdis.keys()): 

222 if self.vdis[uuid].deleted: 222 ↛ 223line 222 didn't jump to line 223, because the condition on line 222 was never true

223 del self.vdis[uuid] 

224 

225 # CA-15607: make sure we are robust to the directory being unmounted beneath 

226 # us (eg by a confused user). Without this we might forget all our VDI references 

227 # which would be a shame. 

228 # For SMB SRs, this path is mountpoint 

229 mount_path = self.path 

230 if self.handles("smb"): 230 ↛ 231line 230 didn't jump to line 231, because the condition on line 230 was never true

231 mount_path = self.mountpoint 

232 

233 if not self.handles("file") and not os.path.ismount(mount_path): 233 ↛ 234line 233 didn't jump to line 234, because the condition on line 233 was never true

234 util.SMlog("Error: FileSR.scan called but directory %s isn't a mountpoint" % mount_path) 

235 raise xs_errors.XenError('SRUnavailable', \ 

236 opterr='not mounted %s' % mount_path) 

237 

238 self._kickGC() 

239 

240 # default behaviour from here on 

241 super(FileSR, self).scan(sr_uuid) 

242 

243 @override 

244 def update(self, sr_uuid) -> None: 

245 if not self._checkmount(): 

246 raise xs_errors.XenError('SRUnavailable', \ 

247 opterr='no such directory %s' % self.path) 

248 self._update(sr_uuid, 0) 

249 

250 def _update(self, sr_uuid, virt_alloc_delta): 

251 valloc = int(self.session.xenapi.SR.get_virtual_allocation(self.sr_ref)) 

252 self.virtual_allocation = valloc + virt_alloc_delta 

253 self.physical_size = self._getsize() 

254 self.physical_utilisation = self._getutilisation() 

255 self._db_update() 

256 

257 @override 

258 def content_type(self, sr_uuid) -> str: 

259 return super(FileSR, self).content_type(sr_uuid) 

260 

261 @override 

262 def vdi(self, uuid) -> VDI.VDI: 

263 return FileVDI(self, uuid) 

264 

265 def added_vdi(self, vdi): 

266 self.vdis[vdi.uuid] = vdi 

267 

268 def deleted_vdi(self, uuid): 

269 if uuid in self.vdis: 

270 del self.vdis[uuid] 

271 

272 @override 

273 def replay(self, uuid) -> None: 

274 try: 

275 file = open(self.path + "/filelog.txt", "r") 

276 data = file.readlines() 

277 file.close() 

278 self._process_replay(data) 

279 except: 

280 raise xs_errors.XenError('SRLog') 

281 

282 def _loadvdis(self): 

283 if self.vdis: 283 ↛ 284line 283 didn't jump to line 284, because the condition on line 283 was never true

284 return 

285 

286 self.image_info = {} 

287 for vdi_type in VDI_COW_TYPES: 

288 extension = VDI_TYPE_TO_EXTENSION[vdi_type] 

289 

290 pattern = os.path.join(self.path, "*%s" % extension) 

291 image_info = {} 

292 

293 cowutil = getCowUtil(vdi_type) 

294 try: 

295 image_info = cowutil.getAllInfoFromVG(pattern, FileVDI.extractUuid) 

296 except util.CommandException as inst: 

297 raise xs_errors.XenError('SRScan', opterr="error VDI-scanning " \ 

298 "path %s (%s)" % (self.path, inst)) 

299 try: 

300 vdi_uuids = [FileVDI.extractUuid(v) for v in util.ioretry(lambda: glob.glob(pattern))] 

301 if len(image_info) != len(vdi_uuids): 

302 util.SMlog("VDI scan of %s returns %d VDIs: %s" % (extension, len(image_info), sorted(image_info))) 

303 util.SMlog("VDI list of %s returns %d VDIs: %s" % (extension, len(vdi_uuids), sorted(vdi_uuids))) 

304 except: 

305 pass 

306 

307 self.image_info.update(image_info) 

308 

309 for uuid, image_info in self.image_info.items(): 

310 if image_info.error: 310 ↛ 311line 310 didn't jump to line 311, because the condition on line 310 was never true

311 raise xs_errors.XenError('SRScan', opterr='uuid=%s' % uuid) 

312 

313 file_vdi = self.vdi(uuid) 

314 file_vdi.cowutil = cowutil 

315 self.vdis[uuid] = file_vdi 

316 

317 # Get the key hash of any encrypted VDIs: 

318 vdi_path = os.path.join(self.path, image_info.path) 

319 key_hash = cowutil.getKeyHash(vdi_path) 

320 self.vdis[uuid].sm_config_override['key_hash'] = key_hash 

321 

322 # raw VDIs and CBT log files 

323 files = util.ioretry(lambda: util.listdir(self.path)) 323 ↛ exitline 323 didn't run the lambda on line 323

324 for fn in files: 324 ↛ 325line 324 didn't jump to line 325, because the loop on line 324 never started

325 if fn.endswith(VdiTypeExtension.RAW): 

326 uuid = fn[:-(len(VdiTypeExtension.RAW))] 

327 self.vdis[uuid] = self.vdi(uuid) 

328 elif fn.endswith(CBTLOG_TAG): 

329 cbt_uuid = fn.split(".")[0] 

330 # If an associated disk exists, update CBT status 

331 # else create new VDI of type cbt_metadata 

332 if cbt_uuid in self.vdis: 

333 self.vdis[cbt_uuid].cbt_enabled = True 

334 else: 

335 new_vdi = self.vdi(cbt_uuid) 

336 new_vdi.ty = "cbt_metadata" 

337 new_vdi.cbt_enabled = True 

338 self.vdis[cbt_uuid] = new_vdi 

339 

340 # Mark parent VDIs as Read-only and generate virtual allocation 

341 self.virtual_allocation = 0 

342 for uuid, vdi in self.vdis.items(): 

343 if vdi.parent: 343 ↛ 344line 343 didn't jump to line 344, because the condition on line 343 was never true

344 if vdi.parent in self.vdis: 

345 self.vdis[vdi.parent].read_only = True 

346 if vdi.parent in geneology: 

347 geneology[vdi.parent].append(uuid) 

348 else: 

349 geneology[vdi.parent] = [uuid] 

350 if not vdi.hidden: 350 ↛ 342line 350 didn't jump to line 342, because the condition on line 350 was never false

351 self.virtual_allocation += (vdi.size) 

352 

353 # now remove all hidden leaf nodes from self.vdis so that they are not 

354 # introduced into the Agent DB when SR is synchronized. With the 

355 # asynchronous GC, a deleted VDI might stay around until the next 

356 # SR.scan, so if we don't ignore hidden leaves we would pick up 

357 # freshly-deleted VDIs as newly-added VDIs 

358 for uuid in list(self.vdis.keys()): 

359 if uuid not in geneology and self.vdis[uuid].hidden: 359 ↛ 360line 359 didn't jump to line 360, because the condition on line 359 was never true

360 util.SMlog("Scan found hidden leaf (%s), ignoring" % uuid) 

361 del self.vdis[uuid] 

362 

363 def _getsize(self): 

364 path = self.path 

365 if self.handles("smb"): 365 ↛ 366line 365 didn't jump to line 366, because the condition on line 365 was never true

366 path = self.linkpath 

367 return util.get_fs_size(path) 

368 

369 def _getutilisation(self): 

370 return util.get_fs_utilisation(self.path) 

371 

372 def _replay(self, logentry): 

373 # all replay commands have the same 5,6,7th arguments 

374 # vdi_command, sr-uuid, vdi-uuid 

375 back_cmd = logentry[5].replace("vdi_", "") 

376 target = self.vdi(logentry[7]) 

377 cmd = getattr(target, back_cmd) 

378 args = [] 

379 for item in logentry[6:]: 

380 item = item.replace("\n", "") 

381 args.append(item) 

382 ret = cmd( * args) 

383 if ret: 

384 print(ret) 

385 

386 def _compare_args(self, a, b): 

387 try: 

388 if a[2] != "log:": 

389 return 1 

390 if b[2] != "end:" and b[2] != "error:": 

391 return 1 

392 if a[3] != b[3]: 

393 return 1 

394 if a[4] != b[4]: 

395 return 1 

396 return 0 

397 except: 

398 return 1 

399 

400 def _process_replay(self, data): 

401 logentries = [] 

402 for logentry in data: 

403 logentry = logentry.split(" ") 

404 logentries.append(logentry) 

405 # we are looking for a log entry that has a log but no end or error 

406 # wkcfix -- recreate (adjusted) logfile 

407 index = 0 

408 while index < len(logentries) - 1: 

409 if self._compare_args(logentries[index], logentries[index + 1]): 

410 self._replay(logentries[index]) 

411 else: 

412 # skip the paired one 

413 index += 1 

414 # next 

415 index += 1 

416 

417 def _kickGC(self): 

418 util.SMlog("Kicking GC") 

419 cleanup.start_gc_service(self.uuid) 

420 

421 def _isbind(self): 

422 # os.path.ismount can't deal with bind mount 

423 st1 = os.stat(self.path) 

424 st2 = os.stat(self.remotepath) 

425 return st1.st_dev == st2.st_dev and st1.st_ino == st2.st_ino 

426 

427 def _checkmount(self) -> bool: 

428 mount_path = self.path 

429 if self.handles("smb"): 429 ↛ 430line 429 didn't jump to line 430, because the condition on line 429 was never true

430 mount_path = self.mountpoint 

431 

432 return util.ioretry(lambda: util.pathexists(mount_path) and \ 

433 (util.ismount(mount_path) or \ 

434 util.pathexists(self.remotepath) and self._isbind())) 

435 

436 # Override in SharedFileSR. 

437 def _check_hardlinks(self) -> bool: 

438 return True 

439 

440class FileVDI(VDI.VDI): 

441 def _resolve_vdi_context_from_path(self, vdi_uuid, maxretry=5, period=2.0): 

442 vdi_formats = ( 

443 (VdiTypeExtension.VHD, VdiType.VHD), 

444 (VdiTypeExtension.QCOW2, VdiType.QCOW2), 

445 (VdiTypeExtension.RAW, VdiType.RAW), 

446 (VdiTypeExtension.CBTLOG, VdiType.CBTLOG) 

447 ) 

448 

449 vdi_uuid_str = str(vdi_uuid) 

450 for try_index in range(maxretry): 

451 for vdi_extension, vdi_type in vdi_formats: 

452 vdi_path = os.path.join(self.sr.path, vdi_uuid_str + vdi_extension) 

453 

454 if util.pathexists(vdi_path): 

455 self.vdi_type = vdi_type 

456 if vdi_type in (VdiType.RAW, VdiType.CBTLOG): 

457 self.hidden = False 

458 self.path = vdi_path 

459 

460 with contextlib.suppress(Exception): 

461 self.cowutil = getCowUtil(self.vdi_type) 

462 

463 return True 

464 

465 util.SMlog("VDI %s not found, try %s of %s" % (vdi_uuid_str, try_index + 1, maxretry)) 

466 if try_index + 1 < maxretry: 

467 time.sleep(period) 

468 

469 return False 

470 

471 @override 

472 def load(self, vdi_uuid) -> None: 

473 self.lock = self.sr.lock 

474 

475 self.sr.srcmd.params['o_direct'] = self.sr.o_direct 

476 

477 if self.sr.srcmd.cmd == "vdi_create": 

478 image_format = None 

479 self.key_hash = None 

480 

481 vdi_sm_config = self.sr.srcmd.params.get("vdi_sm_config") 

482 if vdi_sm_config: 482 ↛ 486line 482 didn't jump to line 486, because the condition on line 482 was never false

483 image_format = self.sr.read_config_image_format(vdi_sm_config) 

484 self.key_hash = vdi_sm_config.get("key_hash") 

485 

486 if not image_format: 486 ↛ 487line 486 didn't jump to line 487, because the condition on line 486 was never true

487 size = int(self.sr.srcmd.params['args'][0]) 

488 # In the case of vdi_create, the first parameter is size. 

489 # We need it to validate the vdi_type choice 

490 for image_format in self.sr.preferred_image_formats: 

491 vdi_type = getVdiTypeFromImageFormat(image_format) 

492 cowutil = getCowUtil(vdi_type) 

493 try: 

494 cowutil.validateAndRoundImageSize(size) 

495 break 

496 except xs_errors.SROSError: 

497 util.SMlog(f"We won't be able to create the VDI with format {vdi_type}.") 

498 # If the last one also fail we still give the vdi_type and cowutil, 

499 # it will fail in the `create` function instead when re-running `validateAndRoundImageSize` 

500 self.vdi_type = self.sr._resolve_vdi_type_from_image_format(image_format) 

501 self.cowutil = getCowUtil(self.vdi_type) 

502 

503 self.path = os.path.join(self.sr.path, "%s%s" % 

504 (vdi_uuid, VDI_TYPE_TO_EXTENSION[self.vdi_type])) 

505 else: 

506 found = self._resolve_vdi_context_from_path(vdi_uuid) 

507 if not found: 507 ↛ 508line 507 didn't jump to line 508, because the condition on line 507 was never true

508 if self.sr.srcmd.cmd == "vdi_delete": 

509 # Could be delete for CBT log file 

510 self.path = os.path.join(self.sr.path, f"{vdi_uuid}.deleted") 

511 return 

512 if self.sr.srcmd.cmd == "vdi_attach_from_config": 

513 return 

514 raise xs_errors.XenError('VDIUnavailable', 

515 opterr="VDI %s not found" % vdi_uuid) 

516 

517 image_info = VdiType.isCowImage(self.vdi_type) and self.sr.image_info.get(vdi_uuid) 

518 if image_info: 

519 # Image info already preloaded: use it instead of querying directly 

520 self.utilisation = image_info.sizePhys 

521 self.size = image_info.sizeVirt 

522 self.hidden = image_info.hidden 

523 if self.hidden: 523 ↛ 524line 523 didn't jump to line 524, because the condition on line 523 was never true

524 self.managed = False 

525 self.parent = image_info.parentUuid 

526 if self.parent: 526 ↛ 527line 526 didn't jump to line 527, because the condition on line 526 was never true

527 self.sm_config_override = {'vhd-parent': self.parent} 

528 else: 

529 self.sm_config_override = {'vhd-parent': None} 

530 return 

531 

532 try: 

533 # Change to the SR directory in case parent 

534 # locator field path has changed 

535 os.chdir(self.sr.path) 

536 except Exception as chdir_exception: 

537 util.SMlog("Unable to change to SR directory, SR unavailable, %s" % 

538 str(chdir_exception)) 

539 raise xs_errors.XenError('SRUnavailable', opterr=str(chdir_exception)) 

540 

541 if util.ioretry( 541 ↛ exitline 541 didn't return from function 'load', because the condition on line 541 was never false

542 lambda: util.pathexists(self.path), 

543 errlist=[errno.EIO, errno.ENOENT]): 

544 try: 

545 st = util.ioretry(lambda: os.stat(self.path), 

546 errlist=[errno.EIO, errno.ENOENT]) 

547 self.utilisation = int(st.st_size) 

548 except util.CommandException as inst: 

549 if inst.code == errno.EIO: 

550 raise xs_errors.XenError('VDILoad', \ 

551 opterr='Failed load VDI information %s' % self.path) 

552 else: 

553 util.SMlog("Stat failed for %s, %s" % ( 

554 self.path, str(inst))) 

555 raise xs_errors.XenError('VDIType', \ 

556 opterr='Invalid VDI type %s' % self.vdi_type) 

557 

558 if self.vdi_type == VdiType.RAW: 558 ↛ 559line 558 didn't jump to line 559, because the condition on line 558 was never true

559 self.exists = True 

560 self.size = self.utilisation 

561 return 

562 

563 if self.vdi_type == VdiType.CBTLOG: 563 ↛ 564line 563 didn't jump to line 564, because the condition on line 563 was never true

564 self.exists = True 

565 self.size = self.utilisation 

566 return 

567 

568 try: 

569 # The VDI might be activated in R/W mode so the VHD footer 

570 # won't be valid, use the back-up one instead. 

571 image_info = self.cowutil.getInfo(self.path, FileVDI.extractUuid, useBackupFooter=True) 

572 

573 if image_info.parentUuid: 573 ↛ 574line 573 didn't jump to line 574, because the condition on line 573 was never true

574 self.parent = image_info.parentUuid 

575 self.sm_config_override = {'vhd-parent': self.parent} 

576 else: 

577 self.parent = "" 

578 self.sm_config_override = {'vhd-parent': None} 

579 self.size = image_info.sizeVirt 

580 self.hidden = image_info.hidden 

581 if self.hidden: 581 ↛ 582line 581 didn't jump to line 582, because the condition on line 581 was never true

582 self.managed = False 

583 self.exists = True 

584 except util.CommandException as inst: 

585 raise xs_errors.XenError('VDILoad', \ 

586 opterr='Failed load VDI information %s' % self.path) 

587 

588 @override 

589 def update(self, sr_uuid, vdi_location) -> None: 

590 self.load(vdi_location) 

591 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

592 self.sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref) 

593 self._db_update() 

594 

595 @override 

596 def create(self, sr_uuid, vdi_uuid, size) -> str: 

597 if util.ioretry(lambda: util.pathexists(self.path)): 597 ↛ 598line 597 didn't jump to line 598, because the condition on line 597 was never true

598 raise xs_errors.XenError('VDIExists') 

599 

600 if VdiType.isCowImage(self.vdi_type): 

601 try: 

602 size = self.cowutil.validateAndRoundImageSize(int(size)) 

603 util.ioretry(lambda: self._create(size, self.path)) 

604 self.size = self.cowutil.getSizeVirt(self.path) 

605 except util.CommandException as inst: 

606 raise xs_errors.XenError('VDICreate', 

607 opterr='error %d' % inst.code) 

608 else: 

609 f = open(self.path, 'w') 

610 f.truncate(int(size)) 

611 f.close() 

612 self.size = size 

613 

614 self.sr.added_vdi(self) 

615 

616 st = util.ioretry(lambda: os.stat(self.path)) 

617 self.utilisation = int(st.st_size) 

618 if not hasattr(self, 'sm_config'): 618 ↛ 620line 618 didn't jump to line 620, because the condition on line 618 was never false

619 self.sm_config = {} 

620 self.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type) 

621 

622 self._db_introduce() 

623 self.sr._update(self.sr.uuid, self.size) 

624 return super(FileVDI, self).get_params() 

625 

626 @override 

627 def delete(self, sr_uuid, vdi_uuid, data_only=False) -> None: 

628 if not util.ioretry(lambda: util.pathexists(self.path)): 

629 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only) 

630 

631 if self.attached: 

632 raise xs_errors.XenError('VDIInUse') 

633 

634 try: 

635 util.force_unlink(self.path) 

636 except Exception as e: 

637 raise xs_errors.XenError( 

638 'VDIDelete', 

639 opterr='Failed to unlink file during deleting VDI: %s' % str(e)) 

640 

641 self.sr.deleted_vdi(vdi_uuid) 

642 # If this is a data_destroy call, don't remove from XAPI db 

643 if not data_only: 

644 self._db_forget() 

645 self.sr._update(self.sr.uuid, -self.size) 

646 self.sr.lock.cleanupAll(vdi_uuid) 

647 self.sr._kickGC() 

648 return super(FileVDI, self).delete(sr_uuid, vdi_uuid, data_only) 

649 

650 @override 

651 def attach(self, sr_uuid, vdi_uuid) -> str: 

652 if self.path is None: 

653 self._resolve_vdi_context_from_path(vdi_uuid) 

654 if not self._checkpath(self.path): 

655 raise xs_errors.XenError('VDIUnavailable', \ 

656 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path)) 

657 try: 

658 self.attached = True 

659 

660 if not hasattr(self, 'xenstore_data'): 

661 self.xenstore_data = {} 

662 

663 self.xenstore_data.update(scsiutil.update_XS_SCSIdata(vdi_uuid, \ 

664 scsiutil.gen_synthetic_page_data(vdi_uuid))) 

665 

666 if self.sr.handles("file"): 

667 # XXX: PR-1255: if these are constants then they should 

668 # be returned by the attach API call, not persisted in the 

669 # pool database. 

670 self.xenstore_data['storage-type'] = 'ext' 

671 return super(FileVDI, self).attach(sr_uuid, vdi_uuid) 

672 except util.CommandException as inst: 

673 raise xs_errors.XenError('VDILoad', opterr='error %d' % inst.code) 

674 

675 @override 

676 def detach(self, sr_uuid, vdi_uuid) -> None: 

677 self.attached = False 

678 

679 @override 

680 def resize(self, sr_uuid, vdi_uuid, size) -> str: 

681 if not self.exists: 

682 raise xs_errors.XenError('VDIUnavailable', \ 

683 opterr='VDI %s unavailable %s' % (vdi_uuid, self.path)) 

684 

685 if not VdiType.isCowImage(self.vdi_type): 

686 raise xs_errors.XenError('Unimplemented') 

687 

688 if self.hidden: 

689 raise xs_errors.XenError('VDIUnavailable', opterr='hidden VDI') 

690 

691 if size < self.size: 

692 util.SMlog('vdi_resize: shrinking not supported: ' + \ 

693 '(current size: %d, new size: %d)' % (self.size, size)) 

694 raise xs_errors.XenError('VDISize', opterr='shrinking not allowed') 

695 

696 if size == self.size: 

697 return VDI.VDI.get_params(self) 

698 

699 # We already checked it is a cow image. 

700 size = self.cowutil.validateAndRoundImageSize(int(size)) 

701 

702 jFile = JOURNAL_FILE_PREFIX + self.uuid 

703 try: 

704 self.cowutil.setSizeVirt(self.path, size, jFile) 

705 except: 

706 # Revert the operation 

707 self.cowutil.revert(self.path, jFile) 

708 raise xs_errors.XenError('VDISize', opterr='resize operation failed') 

709 

710 old_size = self.size 

711 self.size = self.cowutil.getSizeVirt(self.path) 

712 st = util.ioretry(lambda: os.stat(self.path)) 

713 self.utilisation = int(st.st_size) 

714 

715 self._db_update() 

716 self.sr._update(self.sr.uuid, self.size - old_size) 

717 super(FileVDI, self).resize_cbt(self.sr.uuid, self.uuid, self.size) 

718 return VDI.VDI.get_params(self) 

719 

720 @override 

721 def clone(self, sr_uuid, vdi_uuid) -> str: 

722 return self._do_snapshot(sr_uuid, vdi_uuid, VDI.SNAPSHOT_DOUBLE) 

723 

724 @override 

725 def compose(self, sr_uuid, vdi1, vdi2) -> None: 

726 if not VdiType.isCowImage(self.vdi_type): 

727 raise xs_errors.XenError('Unimplemented') 

728 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type] 

729 parent_path = os.path.join(self.sr.path, parent_fn) 

730 assert(util.pathexists(parent_path)) 

731 self.cowutil.setParent(self.path, parent_path, False) 

732 self.cowutil.setHidden(parent_path) 

733 self.sr.session.xenapi.VDI.set_managed(self.sr.srcmd.params['args'][0], False) 

734 # Tell tapdisk the chain has changed 

735 if not blktap2.VDI.tap_refresh(self.session, sr_uuid, vdi2): 

736 raise util.SMException("failed to refresh VDI %s" % self.uuid) 

737 util.SMlog("VDI.compose: relinked %s->%s" % (vdi2, vdi1)) 

738 

739 def reset_leaf(self, sr_uuid, vdi_uuid): 

740 if not VdiType.isCowImage(self.vdi_type): 

741 raise xs_errors.XenError('Unimplemented') 

742 

743 # safety check 

744 if not self.cowutil.hasParent(self.path): 

745 raise util.SMException("ERROR: VDI %s has no parent, " + \ 

746 "will not reset contents" % self.uuid) 

747 

748 self.cowutil.killData(self.path) 

749 

750 @override 

751 def _do_snapshot(self, sr_uuid, vdi_uuid, snapType, 

752 cloneOp=False, secondary=None, cbtlog=None, is_mirror_destination=False) -> str: 

753 # If cbt enabled, save file consistency state 

754 if cbtlog is not None: 754 ↛ 755line 754 didn't jump to line 755, because the condition on line 754 was never true

755 if blktap2.VDI.tap_status(self.session, vdi_uuid): 

756 consistency_state = False 

757 else: 

758 consistency_state = True 

759 util.SMlog("Saving log consistency state of %s for vdi: %s" % 

760 (consistency_state, vdi_uuid)) 

761 else: 

762 consistency_state = None 

763 

764 if not VdiType.isCowImage(self.vdi_type): 764 ↛ 765line 764 didn't jump to line 765, because the condition on line 764 was never true

765 raise xs_errors.XenError('Unimplemented') 

766 

767 if not blktap2.VDI.tap_pause(self.session, sr_uuid, vdi_uuid): 767 ↛ 768line 767 didn't jump to line 768, because the condition on line 767 was never true

768 raise util.SMException("failed to pause VDI %s" % vdi_uuid) 

769 try: 

770 return self._snapshot(snapType, cbtlog, consistency_state, is_mirror_destination) 

771 finally: 

772 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

773 blktap2.VDI.tap_unpause(self.session, sr_uuid, vdi_uuid, secondary) 

774 

775 @override 

776 def _rename(self, src, dst) -> None: 

777 util.SMlog("FileVDI._rename %s to %s" % (src, dst)) 

778 util.ioretry(lambda: os.rename(src, dst)) 

779 

780 def _link(self, src, dst): 

781 util.SMlog("FileVDI._link %s to %s" % (src, dst)) 

782 os.link(src, dst) 

783 

784 def _unlink(self, path): 

785 util.SMlog("FileVDI._unlink %s" % (path)) 

786 os.unlink(path) 

787 

788 def _create_new_parent(self, src, newsrc): 

789 if self.sr._check_hardlinks(): 

790 self._link(src, newsrc) 

791 else: 

792 self._rename(src, newsrc) 

793 

794 def __fist_enospace(self): 

795 raise util.CommandException(28, "cowutil snapshot", reason="No space") 

796 

797 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None, is_mirror_destination=False): 

798 util.SMlog("FileVDI._snapshot for %s (type %s)" % (self.uuid, snap_type)) 

799 

800 args = [] 

801 args.append("vdi_clone") 

802 args.append(self.sr.uuid) 

803 args.append(self.uuid) 

804 

805 dest = None 

806 dst = None 

807 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type] 

808 if snap_type == VDI.SNAPSHOT_DOUBLE: 808 ↛ 813line 808 didn't jump to line 813, because the condition on line 808 was never false

809 dest = util.gen_uuid() 

810 dst = os.path.join(self.sr.path, dest + extension) 

811 args.append(dest) 

812 

813 if self.hidden: 813 ↛ 814line 813 didn't jump to line 814, because the condition on line 813 was never true

814 raise xs_errors.XenError('VDIClone', opterr='hidden VDI') 

815 

816 depth = self.cowutil.getDepth(self.path) 

817 if depth == -1: 817 ↛ 818line 817 didn't jump to line 818, because the condition on line 817 was never true

818 raise xs_errors.XenError('VDIUnavailable', \ 

819 opterr='failed to get image depth') 

820 elif depth >= self.cowutil.getMaxChainLength(): 820 ↛ 821line 820 didn't jump to line 821, because the condition on line 820 was never true

821 raise xs_errors.XenError('SnapshotChainTooLong') 

822 

823 newuuid = util.gen_uuid() 

824 src = self.path 

825 newsrcname = newuuid + extension 

826 newsrc = os.path.join(self.sr.path, newsrcname) 

827 

828 if not self._checkpath(src): 828 ↛ 829line 828 didn't jump to line 829, because the condition on line 828 was never true

829 raise xs_errors.XenError('VDIUnavailable', \ 

830 opterr='VDI %s unavailable %s' % (self.uuid, src)) 

831 

832 # wkcfix: multiphase 

833 util.start_log_entry(self.sr.path, self.path, args) 

834 

835 # We assume the filehandle has been released 

836 try: 

837 self._create_new_parent(src, newsrc) 

838 

839 # Create the snapshot under a temporary name, then rename 

840 # it afterwards. This avoids a small window where it exists 

841 # but is invalid. We do not need to do this for 

842 # snap_type == VDI.SNAPSHOT_DOUBLE because dst never existed 

843 # before so nobody will try to query it. 

844 tmpsrc = "%s.%s" % (src, "new") 

845 # Fault injection site to fail the snapshot with ENOSPACE 

846 util.fistpoint.activate_custom_fn( 

847 "FileSR_fail_snap1", 

848 self.__fist_enospace) 

849 util.ioretry(lambda: self._snap(tmpsrc, newsrcname, is_mirror_destination)) 

850 # SMB3 can return EACCES if we attempt to rename over the 

851 # hardlink leaf too quickly after creating it. 

852 util.ioretry(lambda: self._rename(tmpsrc, src), 

853 errlist=[errno.EIO, errno.EACCES]) 

854 if snap_type == VDI.SNAPSHOT_DOUBLE: 854 ↛ 862line 854 didn't jump to line 862, because the condition on line 854 was never false

855 # Fault injection site to fail the snapshot with ENOSPACE 

856 util.fistpoint.activate_custom_fn( 

857 "FileSR_fail_snap2", 

858 self.__fist_enospace) 

859 util.ioretry(lambda: self._snap(dst, newsrcname)) 

860 # mark the original file (in this case, its newsrc) 

861 # as hidden so that it does not show up in subsequent scans 

862 util.ioretry(lambda: self._mark_hidden(newsrc)) 

863 

864 #Verify parent locator field of both children and delete newsrc if unused 

865 introduce_parent = True 

866 try: 

867 srcparent = self.cowutil.getParent(src, FileVDI.extractUuid) 

868 dstparent = None 

869 if snap_type == VDI.SNAPSHOT_DOUBLE: 869 ↛ 871line 869 didn't jump to line 871, because the condition on line 869 was never false

870 dstparent = self.cowutil.getParent(dst, FileVDI.extractUuid) 

871 if srcparent != newuuid and \ 871 ↛ 875line 871 didn't jump to line 875, because the condition on line 871 was never true

872 (snap_type == VDI.SNAPSHOT_SINGLE or \ 

873 snap_type == VDI.SNAPSHOT_INTERNAL or \ 

874 dstparent != newuuid): 

875 util.ioretry(lambda: self._unlink(newsrc)) 

876 introduce_parent = False 

877 except Exception as e: 

878 raise 

879 

880 # Introduce the new VDI records 

881 leaf_vdi = None 

882 if snap_type == VDI.SNAPSHOT_DOUBLE: 882 ↛ 903line 882 didn't jump to line 903, because the condition on line 882 was never false

883 leaf_vdi = VDI.VDI(self.sr, dest) # user-visible leaf VDI 

884 leaf_vdi.read_only = False 

885 leaf_vdi.location = dest 

886 leaf_vdi.size = self.size 

887 leaf_vdi.utilisation = self.utilisation 

888 leaf_vdi.sm_config = {} 

889 leaf_vdi.sm_config['vhd-parent'] = dstparent 

890 # TODO: fix the raw snapshot case  

891 leaf_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type) 

892 # If the parent is encrypted set the key_hash 

893 # for the new snapshot disk 

894 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

895 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref) 

896 if "key_hash" in sm_config: 896 ↛ 897line 896 didn't jump to line 897, because the condition on line 896 was never true

897 leaf_vdi.sm_config['key_hash'] = sm_config['key_hash'] 

898 # If we have CBT enabled on the VDI, 

899 # set CBT status for the new snapshot disk 

900 if cbtlog: 900 ↛ 901line 900 didn't jump to line 901, because the condition on line 900 was never true

901 leaf_vdi.cbt_enabled = True 

902 

903 base_vdi = None 

904 if introduce_parent: 904 ↛ 918line 904 didn't jump to line 918, because the condition on line 904 was never false

905 base_vdi = VDI.VDI(self.sr, newuuid) # readonly parent 

906 base_vdi.label = "base copy" 

907 base_vdi.read_only = True 

908 base_vdi.location = newuuid 

909 base_vdi.size = self.size 

910 base_vdi.utilisation = self.utilisation 

911 base_vdi.sm_config = {} 

912 # TODO: fix the raw snapshot case  

913 base_vdi.sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type) 

914 grandparent = self.cowutil.getParent(newsrc, FileVDI.extractUuid) 

915 if grandparent: 915 ↛ 918line 915 didn't jump to line 918, because the condition on line 915 was never false

916 base_vdi.sm_config['vhd-parent'] = grandparent 

917 

918 try: 

919 if snap_type == VDI.SNAPSHOT_DOUBLE: 919 ↛ 924line 919 didn't jump to line 924, because the condition on line 919 was never false

920 leaf_vdi_ref = leaf_vdi._db_introduce() 

921 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % \ 

922 (leaf_vdi_ref, dest)) 

923 

924 if introduce_parent: 924 ↛ 928line 924 didn't jump to line 928, because the condition on line 924 was never false

925 base_vdi_ref = base_vdi._db_introduce() 

926 self.session.xenapi.VDI.set_managed(base_vdi_ref, False) 

927 util.SMlog("vdi_clone: introduced VDI: %s (%s)" % (base_vdi_ref, newuuid)) 

928 vdi_ref = self.sr.srcmd.params['vdi_ref'] 

929 sm_config = self.session.xenapi.VDI.get_sm_config(vdi_ref) 

930 sm_config['vhd-parent'] = srcparent 

931 # TODO: fix the raw snapshot case  

932 sm_config["image-format"] = getImageStringFromVdiType(self.vdi_type) 

933 self.session.xenapi.VDI.set_sm_config(vdi_ref, sm_config) 

934 except Exception as e: 

935 util.SMlog("vdi_clone: caught error during VDI.db_introduce: %s" % (str(e))) 

936 # Note it's too late to actually clean stuff up here: the base disk has 

937 # been marked as deleted already. 

938 util.end_log_entry(self.sr.path, self.path, ["error"]) 

939 raise 

940 except util.CommandException as inst: 

941 # XXX: it might be too late if the base disk has been marked as deleted! 

942 self._clonecleanup(src, dst, newsrc) 

943 util.end_log_entry(self.sr.path, self.path, ["error"]) 

944 raise xs_errors.XenError('VDIClone', 

945 opterr='VDI clone failed error %d' % inst.code) 

946 

947 # Update cbt files if user created snapshot (SNAPSHOT_DOUBLE) 

948 if snap_type == VDI.SNAPSHOT_DOUBLE and cbtlog: 948 ↛ 949line 948 didn't jump to line 949, because the condition on line 948 was never true

949 try: 

950 self._cbt_snapshot(dest, cbt_consistency) 

951 except: 

952 # CBT operation failed. 

953 util.end_log_entry(self.sr.path, self.path, ["error"]) 

954 raise 

955 

956 util.end_log_entry(self.sr.path, self.path, ["done"]) 

957 if snap_type != VDI.SNAPSHOT_INTERNAL: 957 ↛ 960line 957 didn't jump to line 960, because the condition on line 957 was never false

958 self.sr._update(self.sr.uuid, self.size) 

959 # Return info on the new user-visible leaf VDI 

960 ret_vdi = leaf_vdi 

961 if not ret_vdi: 961 ↛ 962line 961 didn't jump to line 962, because the condition on line 961 was never true

962 ret_vdi = base_vdi 

963 if not ret_vdi: 963 ↛ 964line 963 didn't jump to line 964, because the condition on line 963 was never true

964 ret_vdi = self 

965 return ret_vdi.get_params() 

966 

967 @override 

968 def get_params(self) -> str: 

969 if not self._checkpath(self.path): 

970 raise xs_errors.XenError('VDIUnavailable', \ 

971 opterr='VDI %s unavailable %s' % (self.uuid, self.path)) 

972 return super(FileVDI, self).get_params() 

973 

974 def _snap(self, child, parent, is_mirror_destination=False): 

975 self.cowutil.snapshot(child, parent, self.vdi_type == VdiType.RAW, is_mirror_image=is_mirror_destination) 

976 

977 def _clonecleanup(self, src, dst, newsrc): 

978 try: 

979 if dst: 979 ↛ 983line 979 didn't jump to line 983, because the condition on line 979 was never false

980 util.ioretry(lambda: self._unlink(dst)) 

981 except util.CommandException as inst: 

982 pass 

983 try: 

984 if util.ioretry(lambda: util.pathexists(newsrc)): 984 ↛ exitline 984 didn't return from function '_clonecleanup', because the condition on line 984 was never false

985 stats = os.stat(newsrc) 

986 # Check if we have more than one link to newsrc 

987 if (stats.st_nlink > 1): 

988 util.ioretry(lambda: self._unlink(newsrc)) 

989 elif not self._is_hidden(newsrc): 989 ↛ exitline 989 didn't return from function '_clonecleanup', because the condition on line 989 was never false

990 self._rename(newsrc, src) 

991 except util.CommandException as inst: 

992 pass 

993 

994 def _checkpath(self, path): 

995 try: 

996 if not util.ioretry(lambda: util.pathexists(path)): 996 ↛ 997line 996 didn't jump to line 997, because the condition on line 996 was never true

997 return False 

998 return True 

999 except util.CommandException as inst: 

1000 raise xs_errors.XenError('EIO', \ 

1001 opterr='IO error checking path %s' % path) 

1002 

1003 def _create(self, size, path): 

1004 self.cowutil.create(path, size, False) 

1005 if self.key_hash: 1005 ↛ 1006line 1005 didn't jump to line 1006, because the condition on line 1005 was never true

1006 self.cowutil.setKey(path, self.key_hash) 

1007 

1008 def _mark_hidden(self, path): 

1009 self.cowutil.setHidden(path, True) 

1010 self.hidden = 1 

1011 

1012 def _is_hidden(self, path): 

1013 return self.cowutil.getHidden(path) == 1 

1014 

1015 @staticmethod 

1016 def extractUuid(path: str) -> str: 

1017 fileName = os.path.basename(path) 

1018 return os.path.splitext(fileName)[0] 

1019 

1020 @override 

1021 def generate_config(self, sr_uuid, vdi_uuid) -> str: 

1022 """ 

1023 Generate the XML config required to attach and activate 

1024 a VDI for use when XAPI is not running. Attach and 

1025 activation is handled by vdi_attach_from_config below. 

1026 """ 

1027 util.SMlog("FileVDI.generate_config") 

1028 if not util.pathexists(self.path): 1028 ↛ 1029line 1028 didn't jump to line 1029, because the condition on line 1028 was never true

1029 raise xs_errors.XenError('VDIUnavailable') 

1030 resp = {} 

1031 resp['device_config'] = self.sr.dconf 

1032 resp['sr_uuid'] = sr_uuid 

1033 resp['vdi_uuid'] = vdi_uuid 

1034 resp['command'] = 'vdi_attach_from_config' 

1035 # Return the 'config' encoded within a normal XMLRPC response so that 

1036 # we can use the regular response/error parsing code. 

1037 config = xmlrpc.client.dumps(tuple([resp]), "vdi_attach_from_config") 

1038 return xmlrpc.client.dumps((config, ), "", True) 

1039 

1040 @override 

1041 def attach_from_config(self, sr_uuid, vdi_uuid) -> str: 

1042 """ 

1043 Attach and activate a VDI using config generated by 

1044 vdi_generate_config above. This is used for cases such as 

1045 the HA state-file and the redo-log. 

1046 """ 

1047 util.SMlog("FileVDI.attach_from_config") 

1048 try: 

1049 if not util.pathexists(self.sr.path): 

1050 return self.sr.attach(sr_uuid) 

1051 except: 

1052 util.logException("FileVDI.attach_from_config") 

1053 raise xs_errors.XenError( 

1054 'SRUnavailable', 

1055 opterr='Unable to attach from config' 

1056 ) 

1057 return '' 

1058 

1059 @override 

1060 def _create_cbt_log(self) -> str: 

1061 # Create CBT log file 

1062 # Name: <vdi_uuid>.cbtlog 

1063 #Handle if file already exists 

1064 log_path = self._get_cbt_logpath(self.uuid) 

1065 open_file = open(log_path, "w+") 

1066 open_file.close() 

1067 return super(FileVDI, self)._create_cbt_log() 

1068 

1069 @override 

1070 def _delete_cbt_log(self) -> None: 

1071 logPath = self._get_cbt_logpath(self.uuid) 

1072 try: 

1073 os.remove(logPath) 

1074 except OSError as e: 

1075 if e.errno != errno.ENOENT: 

1076 raise 

1077 

1078 @override 

1079 def _cbt_log_exists(self, logpath) -> bool: 

1080 return util.pathexists(logpath) 

1081 

1082 

1083class SharedFileSR(FileSR): 

1084 """ 

1085 FileSR subclass for SRs that use shared network storage 

1086 """ 

1087 

1088 def _check_writable(self): 

1089 """ 

1090 Checks that the filesystem being used by the SR can be written to, 

1091 raising an exception if it can't. 

1092 """ 

1093 test_name = os.path.join(self.path, str(uuid4())) 

1094 try: 

1095 open(test_name, 'ab').close() 

1096 except OSError as e: 

1097 util.SMlog("Cannot write to SR file system: %s" % e) 

1098 raise xs_errors.XenError('SharedFileSystemNoWrite') 

1099 finally: 

1100 util.force_unlink(test_name) 

1101 

1102 def _raise_hardlink_error(self): 

1103 raise OSError(524, "Unknown error 524") 

1104 

1105 @override 

1106 def _check_hardlinks(self) -> bool: 

1107 hardlink_conf = self._read_hardlink_conf() 

1108 if hardlink_conf is not None: 1108 ↛ 1109line 1108 didn't jump to line 1109, because the condition on line 1108 was never true

1109 return hardlink_conf 

1110 

1111 test_name = os.path.join(self.path, str(uuid4())) 

1112 open(test_name, 'ab').close() 

1113 

1114 link_name = '%s.new' % test_name 

1115 try: 

1116 # XSI-1100: Let tests simulate failure of the link operation 

1117 util.fistpoint.activate_custom_fn( 

1118 "FileSR_fail_hardlink", 

1119 self._raise_hardlink_error) 

1120 

1121 os.link(test_name, link_name) 

1122 self._write_hardlink_conf(supported=True) 

1123 return True 

1124 except OSError: 

1125 self._write_hardlink_conf(supported=False) 

1126 

1127 msg = "File system for SR %s does not support hardlinks, crash " \ 

1128 "consistency of snapshots cannot be assured" % self.uuid 

1129 util.SMlog(msg, priority=util.LOG_WARNING) 

1130 # Note: session can be not set during attach/detach_from_config calls. 

1131 if self.session: 1131 ↛ 1140line 1131 didn't jump to line 1140, because the condition on line 1131 was never false

1132 try: 

1133 self.session.xenapi.message.create( 

1134 "sr_does_not_support_hardlinks", 2, "SR", self.uuid, 

1135 msg) 

1136 except XenAPI.Failure: 

1137 # Might already be set and checking has TOCTOU issues 

1138 pass 

1139 finally: 

1140 util.force_unlink(link_name) 

1141 util.force_unlink(test_name) 

1142 

1143 return False 

1144 

1145 def _get_hardlink_conf_path(self): 

1146 return os.path.join(self.path, 'sm-hardlink.conf') 

1147 

1148 def _read_hardlink_conf(self) -> Optional[bool]: 

1149 try: 

1150 with open(self._get_hardlink_conf_path(), 'r') as f: 

1151 try: 

1152 return bool(int(f.read())) 

1153 except Exception as e: 

1154 # If we can't read, assume the file is empty and test for hardlink support. 

1155 return None 

1156 except IOError as e: 

1157 if e.errno == errno.ENOENT: 

1158 # If the config file doesn't exist, assume we want to support hardlinks. 

1159 return None 

1160 util.SMlog('Failed to read hardlink conf: {}'.format(e)) 

1161 # Can be caused by a concurrent access, not a major issue. 

1162 return None 

1163 

1164 def _write_hardlink_conf(self, supported): 

1165 try: 

1166 with open(self._get_hardlink_conf_path(), 'w') as f: 

1167 f.write('1' if supported else '0') 

1168 except Exception as e: 

1169 # Can be caused by a concurrent access, not a major issue. 

1170 util.SMlog('Failed to write hardlink conf: {}'.format(e)) 

1171 

1172if __name__ == '__main__': 1172 ↛ 1173line 1172 didn't jump to line 1173, because the condition on line 1172 was never true

1173 SRCommand.run(FileSR, DRIVER_INFO) 

1174else: 

1175 SR.registerSR(FileSR)