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 util 

26import scsiutil 

27import lock 

28import os 

29import errno 

30import xs_errors 

31import cleanup 

32import blktap2 

33import time 

34import glob 

35from uuid import uuid4 

36from cowutil import getCowUtil, getImageStringFromVdiType, getVdiTypeFromImageFormat 

37from vditype import VdiType, VdiTypeExtension, VDI_COW_TYPES, VDI_TYPE_TO_EXTENSION 

38import xmlrpc.client 

39import XenAPI # pylint: disable=import-error 

40from constants import CBTLOG_TAG 

41 

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

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

44 "VDI_CREATE", "VDI_DELETE", "VDI_ATTACH", "VDI_DETACH", \ 

45 "VDI_CLONE", "VDI_SNAPSHOT", "VDI_RESIZE", "VDI_MIRROR", 

46 "VDI_GENERATE_CONFIG", "ATOMIC_PAUSE", "VDI_CONFIG_CBT", 

47 "VDI_ACTIVATE", "VDI_DEACTIVATE", "THIN_PROVISIONING"] 

48 

49CONFIGURATION = [ 

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

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

52] 

53 

54DRIVER_INFO = { 

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

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

57 'vendor': 'Citrix Systems Inc', 

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

59 'driver_version': '1.0', 

60 'required_api_version': '1.0', 

61 'capabilities': CAPABILITIES, 

62 'configuration': CONFIGURATION 

63 } 

64 

65JOURNAL_FILE_PREFIX = ".journal-" 

66 

67OPS_EXCLUSIVE = [ 

68 "sr_create", "sr_delete", "sr_probe", "sr_attach", "sr_detach", 

69 "sr_scan", "vdi_init", "vdi_create", "vdi_delete", "vdi_attach", 

70 "vdi_detach", "vdi_resize_online", "vdi_snapshot", "vdi_clone"] 

71 

72DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

73 

74 

75class FileSR(SR.SR): 

76 """Local file storage repository""" 

77 

78 SR_TYPE = "file" 

79 

80 @override 

81 @staticmethod 

82 def handles(srtype) -> bool: 

83 return srtype == 'file' 

84 

85 def _check_o_direct(self): 

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

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

88 o_direct = other_config.get("o_direct") 

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

90 else: 

91 self.o_direct = True 

92 

93 def __init__(self, srcmd, sr_uuid): 

94 # We call SR.SR.__init__ explicitly because 

95 # "super" sometimes failed due to circular imports 

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

97 self.image_info = {} 

98 self._init_image_formats() 

99 self._check_o_direct() 

100 

101 @override 

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

103 self.ops_exclusive = OPS_EXCLUSIVE 

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

105 self.sr_vditype = SR.DEFAULT_TAP 

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

107 raise xs_errors.XenError('ConfigLocationMissing') 

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

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

110 self.linkpath = self.path 

111 self.mountpoint = self.path 

112 self.attached = False 

113 self.driver_config = DRIVER_CONFIG 

114 

115 @override 

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

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

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

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

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

121 try: 

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

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

124 raise xs_errors.XenError('SRExists') 

125 else: 

126 try: 

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

128 except util.CommandException as inst: 

129 if inst.code == errno.EEXIST: 

130 raise xs_errors.XenError('SRExists') 

131 else: 

132 raise xs_errors.XenError('FileSRCreate', \ 

133 opterr='directory creation failure %d' \ 

134 % inst.code) 

135 except: 

136 raise xs_errors.XenError('FileSRCreate') 

137 

138 @override 

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

140 self.attach(sr_uuid) 

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

142 

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

144 # files that are non VDI's 

145 try: 

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

147 #Load the VDI list 

148 self._loadvdis() 

149 for uuid in self.vdis: 

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

151 raise xs_errors.XenError('SRNotEmpty', \ 

152 opterr='VDIs still exist in SR') 

153 

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

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

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

157 try: 

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

159 except util.CommandException as inst: 

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

161 inst.code != errno.EISDIR: 

162 raise xs_errors.XenError('FileSRDelete', \ 

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

164 % (fullpath, inst.code)) 

165 self.detach(sr_uuid) 

166 except util.CommandException as inst: 

167 self.detach(sr_uuid) 

168 raise xs_errors.XenError('FileSRDelete', \ 

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

170 

171 @override 

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

173 self.attach_and_bind(sr_uuid) 

174 

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

176 if not self._checkmount(): 

177 try: 

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

179 except util.CommandException as inst: 

180 if inst.code != errno.EEXIST: 

181 raise xs_errors.XenError("FileSRCreate", \ 

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

183 try: 

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

185 if bind: 

186 cmd.append("--bind") 

187 util.pread(cmd) 

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

189 except util.CommandException as inst: 

190 raise xs_errors.XenError('FileSRCreate', \ 

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

192 self.attached = True 

193 

194 @override 

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

196 if self._checkmount(): 

197 try: 

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

199 cleanup.abort(self.uuid) 

200 os.chdir(SR.MOUNT_BASE) 

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

202 os.rmdir(self.path) 

203 except Exception as e: 

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

205 self.attached = False 

206 

207 @override 

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

209 if not self._checkmount(): 

210 raise xs_errors.XenError('SRUnavailable', \ 

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

212 

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

214 self._loadvdis() 

215 

216 if not self.passthrough: 

217 self.physical_size = self._getsize() 

218 self.physical_utilisation = self._getutilisation() 

219 

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

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

222 del self.vdis[uuid] 

223 

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

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

226 # which would be a shame. 

227 # For SMB SRs, this path is mountpoint 

228 mount_path = self.path 

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

230 mount_path = self.mountpoint 

231 

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

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

234 raise xs_errors.XenError('SRUnavailable', \ 

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

236 

237 self._kickGC() 

238 

239 # default behaviour from here on 

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

241 

242 @override 

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

244 if not self._checkmount(): 

245 raise xs_errors.XenError('SRUnavailable', \ 

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

247 self._update(sr_uuid, 0) 

248 

249 def _update(self, sr_uuid, virt_alloc_delta): 

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

251 self.virtual_allocation = valloc + virt_alloc_delta 

252 self.physical_size = self._getsize() 

253 self.physical_utilisation = self._getutilisation() 

254 self._db_update() 

255 

256 @override 

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

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

259 

260 @override 

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

262 return FileVDI(self, uuid) 

263 

264 def added_vdi(self, vdi): 

265 self.vdis[vdi.uuid] = vdi 

266 

267 def deleted_vdi(self, uuid): 

268 if uuid in self.vdis: 

269 del self.vdis[uuid] 

270 

271 @override 

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

273 try: 

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

275 data = file.readlines() 

276 file.close() 

277 self._process_replay(data) 

278 except: 

279 raise xs_errors.XenError('SRLog') 

280 

281 def _loadvdis(self): 

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

283 return 

284 

285 self.image_info = {} 

286 for vdi_type in VDI_COW_TYPES: 

287 extension = VDI_TYPE_TO_EXTENSION[vdi_type] 

288 

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

290 image_info = {} 

291 

292 cowutil = getCowUtil(vdi_type) 

293 try: 

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

295 except util.CommandException as inst: 

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

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

298 try: 

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

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

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

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

303 except: 

304 pass 

305 

306 self.image_info.update(image_info) 

307 

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

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

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

311 

312 file_vdi = self.vdi(uuid) 

313 file_vdi.cowutil = cowutil 

314 self.vdis[uuid] = file_vdi 

315 

316 # Get the key hash of any encrypted VDIs: 

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

318 key_hash = cowutil.getKeyHash(vdi_path) 

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

320 

321 # raw VDIs and CBT log files 

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

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

324 if fn.endswith(VdiTypeExtension.RAW): 

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

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

327 elif fn.endswith(CBTLOG_TAG): 

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

329 # If an associated disk exists, update CBT status 

330 # else create new VDI of type cbt_metadata 

331 if cbt_uuid in self.vdis: 

332 self.vdis[cbt_uuid].cbt_enabled = True 

333 else: 

334 new_vdi = self.vdi(cbt_uuid) 

335 new_vdi.ty = "cbt_metadata" 

336 new_vdi.cbt_enabled = True 

337 self.vdis[cbt_uuid] = new_vdi 

338 

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

340 self.virtual_allocation = 0 

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

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

343 if vdi.parent in self.vdis: 

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

345 if vdi.parent in geneology: 

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

347 else: 

348 geneology[vdi.parent] = [uuid] 

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

350 self.virtual_allocation += (vdi.size) 

351 

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

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

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

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

356 # freshly-deleted VDIs as newly-added VDIs 

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

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

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

360 del self.vdis[uuid] 

361 

362 def _getsize(self): 

363 path = self.path 

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

365 path = self.linkpath 

366 return util.get_fs_size(path) 

367 

368 def _getutilisation(self): 

369 return util.get_fs_utilisation(self.path) 

370 

371 def _replay(self, logentry): 

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

373 # vdi_command, sr-uuid, vdi-uuid 

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

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

376 cmd = getattr(target, back_cmd) 

377 args = [] 

378 for item in logentry[6:]: 

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

380 args.append(item) 

381 ret = cmd( * args) 

382 if ret: 

383 print(ret) 

384 

385 def _compare_args(self, a, b): 

386 try: 

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

388 return 1 

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

390 return 1 

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

392 return 1 

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

394 return 1 

395 return 0 

396 except: 

397 return 1 

398 

399 def _process_replay(self, data): 

400 logentries = [] 

401 for logentry in data: 

402 logentry = logentry.split(" ") 

403 logentries.append(logentry) 

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

405 # wkcfix -- recreate (adjusted) logfile 

406 index = 0 

407 while index < len(logentries) - 1: 

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

409 self._replay(logentries[index]) 

410 else: 

411 # skip the paired one 

412 index += 1 

413 # next 

414 index += 1 

415 

416 def _kickGC(self): 

417 util.SMlog("Kicking GC") 

418 cleanup.start_gc_service(self.uuid) 

419 

420 def _isbind(self): 

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

422 st1 = os.stat(self.path) 

423 st2 = os.stat(self.remotepath) 

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

425 

426 def _checkmount(self) -> bool: 

427 mount_path = self.path 

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

429 mount_path = self.mountpoint 

430 

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

432 (util.ismount(mount_path) or \ 

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

434 

435 # Override in SharedFileSR. 

436 def _check_hardlinks(self) -> bool: 

437 return True 

438 

439class FileVDI(VDI.VDI): 

440 PARAM_RAW = "raw" 

441 PARAM_VHD = "vhd" 

442 PARAM_QCOW2 = "qcow2" 

443 

444 def _find_path_with_retries(self, vdi_uuid, maxretry=5, period=2.0): 

445 raw_path = os.path.join(self.sr.path, "%s.%s" % \ 

446 (vdi_uuid, self.PARAM_RAW)) 

447 vhd_path = os.path.join(self.sr.path, "%s.%s" % \ 

448 (vdi_uuid, self.PARAM_VHD)) 

449 qcow2_path = os.path.join(self.sr.path, "%s.%s" % \ 

450 (vdi_uuid, self.PARAM_QCOW2)) 

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

452 (vdi_uuid, CBTLOG_TAG)) 

453 found = False 

454 tries = 0 

455 while tries < maxretry and not found: 

456 tries += 1 

457 if util.ioretry(lambda: util.pathexists(vhd_path)): 

458 self.vdi_type = VdiType.VHD 

459 self.path = vhd_path 

460 found = True 

461 elif util.ioretry(lambda: util.pathexists(qcow2_path)): 461 ↛ 462line 461 didn't jump to line 462, because the condition on line 461 was never true

462 self.vdi_type = VdiType.QCOW2 

463 self.path = qcow2_path 

464 found = True 

465 elif util.ioretry(lambda: util.pathexists(raw_path)): 

466 self.vdi_type = VdiType.RAW 

467 self.path = raw_path 

468 self.hidden = False 

469 found = True 

470 elif util.ioretry(lambda: util.pathexists(cbt_path)): 470 ↛ 471line 470 didn't jump to line 471, because the condition on line 470 was never true

471 self.vdi_type = VdiType.CBTLOG 

472 self.path = cbt_path 

473 self.hidden = False 

474 found = True 

475 

476 if found: 

477 try: 

478 self.cowutil = getCowUtil(self.vdi_type) 

479 except: 

480 pass 

481 else: 

482 util.SMlog("VDI %s not found, retry %s of %s" % (vdi_uuid, tries, maxretry)) 

483 time.sleep(period) 

484 

485 return found 

486 

487 @override 

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

489 self.lock = self.sr.lock 

490 

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

492 

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

494 image_format = None 

495 self.key_hash = None 

496 

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

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

499 image_format = self.sr.read_config_image_format(vdi_sm_config) 

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

501 

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

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

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

505 # We need it to validate the vdi_type choice 

506 for image_format in self.sr.preferred_image_formats: 

507 vdi_type = getVdiTypeFromImageFormat(image_format) 

508 cowutil = getCowUtil(vdi_type) 

509 try: 

510 cowutil.validateAndRoundImageSize(size) 

511 break 

512 except xs_errors.SROSError: 

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

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

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

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

517 self.cowutil = getCowUtil(self.vdi_type) 

518 

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

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

521 else: 

522 found = self._find_path_with_retries(vdi_uuid) 

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

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

525 # Could be delete for CBT log file 

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

527 return 

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

529 return 

530 raise xs_errors.XenError('VDIUnavailable', 

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

532 

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

534 if image_info: 

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

536 self.utilisation = image_info.sizePhys 

537 self.size = image_info.sizeVirt 

538 self.hidden = image_info.hidden 

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

540 self.managed = False 

541 self.parent = image_info.parentUuid 

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

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

544 else: 

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

546 return 

547 

548 try: 

549 # Change to the SR directory in case parent 

550 # locator field path has changed 

551 os.chdir(self.sr.path) 

552 except Exception as chdir_exception: 

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

554 str(chdir_exception)) 

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

556 

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

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

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

560 try: 

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

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

563 self.utilisation = int(st.st_size) 

564 except util.CommandException as inst: 

565 if inst.code == errno.EIO: 

566 raise xs_errors.XenError('VDILoad', \ 

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

568 else: 

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

570 self.path, str(inst))) 

571 raise xs_errors.XenError('VDIType', \ 

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

573 

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

575 self.exists = True 

576 self.size = self.utilisation 

577 self.sm_config_override = {'type': self.PARAM_RAW} 

578 return 

579 

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

581 self.exists = True 

582 self.size = self.utilisation 

583 return 

584 

585 try: 

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

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

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

589 

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

591 self.parent = image_info.parentUuid 

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

593 else: 

594 self.parent = "" 

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

596 self.size = image_info.sizeVirt 

597 self.hidden = image_info.hidden 

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

599 self.managed = False 

600 self.exists = True 

601 except util.CommandException as inst: 

602 raise xs_errors.XenError('VDILoad', \ 

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

604 

605 @override 

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

607 self.load(vdi_location) 

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

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

610 self._db_update() 

611 

612 @override 

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

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

615 raise xs_errors.XenError('VDIExists') 

616 

617 if VdiType.isCowImage(self.vdi_type): 

618 try: 

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

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

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

622 except util.CommandException as inst: 

623 raise xs_errors.XenError('VDICreate', 

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

625 else: 

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

627 f.truncate(int(size)) 

628 f.close() 

629 self.size = size 

630 

631 self.sr.added_vdi(self) 

632 

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

634 self.utilisation = int(st.st_size) 

635 if self.vdi_type == VdiType.RAW: 

636 # Legacy code. 

637 self.sm_config = {"type": self.PARAM_RAW} 

638 if not hasattr(self, 'sm_config'): 

639 self.sm_config = {} 

640 self.sm_config = {"image-format": getImageStringFromVdiType(self.vdi_type)} 

641 

642 self._db_introduce() 

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

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

645 

646 @override 

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

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

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

650 

651 if self.attached: 

652 raise xs_errors.XenError('VDIInUse') 

653 

654 try: 

655 util.force_unlink(self.path) 

656 except Exception as e: 

657 raise xs_errors.XenError( 

658 'VDIDelete', 

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

660 

661 self.sr.deleted_vdi(vdi_uuid) 

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

663 if not data_only: 

664 self._db_forget() 

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

666 self.sr.lock.cleanupAll(vdi_uuid) 

667 self.sr._kickGC() 

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

669 

670 @override 

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

672 if self.path is None: 

673 self._find_path_with_retries(vdi_uuid) 

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

675 raise xs_errors.XenError('VDIUnavailable', \ 

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

677 try: 

678 self.attached = True 

679 

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

681 self.xenstore_data = {} 

682 

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

684 scsiutil.gen_synthetic_page_data(vdi_uuid))) 

685 

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

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

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

689 # pool database. 

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

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

692 except util.CommandException as inst: 

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

694 

695 @override 

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

697 self.attached = False 

698 

699 @override 

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

701 if not self.exists: 

702 raise xs_errors.XenError('VDIUnavailable', \ 

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

704 

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

706 raise xs_errors.XenError('Unimplemented') 

707 

708 if self.hidden: 

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

710 

711 if size < self.size: 

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

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

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

715 

716 if size == self.size: 

717 return VDI.VDI.get_params(self) 

718 

719 # We already checked it is a cow image. 

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

721 

722 jFile = JOURNAL_FILE_PREFIX + self.uuid 

723 try: 

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

725 except: 

726 # Revert the operation 

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

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

729 

730 old_size = self.size 

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

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

733 self.utilisation = int(st.st_size) 

734 

735 self._db_update() 

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

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

738 return VDI.VDI.get_params(self) 

739 

740 @override 

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

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

743 

744 @override 

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

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

747 raise xs_errors.XenError('Unimplemented') 

748 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

750 assert(util.pathexists(parent_path)) 

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

752 self.cowutil.setHidden(parent_path) 

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

754 # Tell tapdisk the chain has changed 

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

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

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

758 

759 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

761 raise xs_errors.XenError('Unimplemented') 

762 

763 # safety check 

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

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

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

767 

768 self.cowutil.killData(self.path) 

769 

770 @override 

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

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

773 # If cbt enabled, save file consistency state 

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

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

776 consistency_state = False 

777 else: 

778 consistency_state = True 

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

780 (consistency_state, vdi_uuid)) 

781 else: 

782 consistency_state = None 

783 

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

785 raise xs_errors.XenError('Unimplemented') 

786 

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

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

789 try: 

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

791 finally: 

792 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

794 

795 @override 

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

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

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

799 

800 def _link(self, src, dst): 

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

802 os.link(src, dst) 

803 

804 def _unlink(self, path): 

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

806 os.unlink(path) 

807 

808 def _create_new_parent(self, src, newsrc): 

809 if self.sr._check_hardlinks(): 

810 self._link(src, newsrc) 

811 else: 

812 self._rename(src, newsrc) 

813 

814 def __fist_enospace(self): 

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

816 

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

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

819 

820 args = [] 

821 args.append("vdi_clone") 

822 args.append(self.sr.uuid) 

823 args.append(self.uuid) 

824 

825 dest = None 

826 dst = None 

827 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

829 dest = util.gen_uuid() 

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

831 args.append(dest) 

832 

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

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

835 

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

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

838 raise xs_errors.XenError('VDIUnavailable', \ 

839 opterr='failed to get image depth') 

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

841 raise xs_errors.XenError('SnapshotChainTooLong') 

842 

843 newuuid = util.gen_uuid() 

844 src = self.path 

845 newsrcname = newuuid + extension 

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

847 

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

849 raise xs_errors.XenError('VDIUnavailable', \ 

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

851 

852 # wkcfix: multiphase 

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

854 

855 # We assume the filehandle has been released 

856 try: 

857 self._create_new_parent(src, newsrc) 

858 

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

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

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

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

863 # before so nobody will try to query it. 

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

865 # Fault injection site to fail the snapshot with ENOSPACE 

866 util.fistpoint.activate_custom_fn( 

867 "FileSR_fail_snap1", 

868 self.__fist_enospace) 

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

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

871 # hardlink leaf too quickly after creating it. 

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

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

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

875 # Fault injection site to fail the snapshot with ENOSPACE 

876 util.fistpoint.activate_custom_fn( 

877 "FileSR_fail_snap2", 

878 self.__fist_enospace) 

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

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

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

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

883 

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

885 introduce_parent = True 

886 try: 

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

888 dstparent = None 

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

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

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

892 (snap_type == VDI.SNAPSHOT_SINGLE or \ 

893 snap_type == VDI.SNAPSHOT_INTERNAL or \ 

894 dstparent != newuuid): 

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

896 introduce_parent = False 

897 except Exception as e: 

898 raise 

899 

900 # Introduce the new VDI records 

901 leaf_vdi = None 

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

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

904 leaf_vdi.read_only = False 

905 leaf_vdi.location = dest 

906 leaf_vdi.size = self.size 

907 leaf_vdi.utilisation = self.utilisation 

908 leaf_vdi.sm_config = {} 

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

910 # TODO: fix the raw snapshot case  

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

912 # If the parent is encrypted set the key_hash 

913 # for the new snapshot disk 

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

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

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

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

918 # If we have CBT enabled on the VDI, 

919 # set CBT status for the new snapshot disk 

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

921 leaf_vdi.cbt_enabled = True 

922 

923 base_vdi = None 

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

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

926 base_vdi.label = "base copy" 

927 base_vdi.read_only = True 

928 base_vdi.location = newuuid 

929 base_vdi.size = self.size 

930 base_vdi.utilisation = self.utilisation 

931 base_vdi.sm_config = {} 

932 # TODO: fix the raw snapshot case  

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

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

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

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

937 

938 try: 

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

940 leaf_vdi_ref = leaf_vdi._db_introduce() 

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

942 (leaf_vdi_ref, dest)) 

943 

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

945 base_vdi_ref = base_vdi._db_introduce() 

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

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

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

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

950 sm_config['vhd-parent'] = srcparent 

951 # TODO: fix the raw snapshot case  

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

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

954 except Exception as e: 

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

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

957 # been marked as deleted already. 

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

959 raise 

960 except util.CommandException as inst: 

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

962 self._clonecleanup(src, dst, newsrc) 

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

964 raise xs_errors.XenError('VDIClone', 

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

966 

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

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

969 try: 

970 self._cbt_snapshot(dest, cbt_consistency) 

971 except: 

972 # CBT operation failed. 

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

974 raise 

975 

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

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

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

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

980 ret_vdi = leaf_vdi 

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

982 ret_vdi = base_vdi 

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

984 ret_vdi = self 

985 return ret_vdi.get_params() 

986 

987 @override 

988 def get_params(self) -> str: 

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

990 raise xs_errors.XenError('VDIUnavailable', \ 

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

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

993 

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

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

996 

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

998 try: 

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

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

1001 except util.CommandException as inst: 

1002 pass 

1003 try: 

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

1005 stats = os.stat(newsrc) 

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

1007 if (stats.st_nlink > 1): 

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

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

1010 self._rename(newsrc, src) 

1011 except util.CommandException as inst: 

1012 pass 

1013 

1014 def _checkpath(self, path): 

1015 try: 

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

1017 return False 

1018 return True 

1019 except util.CommandException as inst: 

1020 raise xs_errors.XenError('EIO', \ 

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

1022 

1023 def _create(self, size, path): 

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

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

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

1027 

1028 def _mark_hidden(self, path): 

1029 self.cowutil.setHidden(path, True) 

1030 self.hidden = 1 

1031 

1032 def _is_hidden(self, path): 

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

1034 

1035 @staticmethod 

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

1037 fileName = os.path.basename(path) 

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

1039 

1040 @override 

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

1042 """ 

1043 Generate the XML config required to attach and activate 

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

1045 activation is handled by vdi_attach_from_config below. 

1046 """ 

1047 util.SMlog("FileVDI.generate_config") 

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

1049 raise xs_errors.XenError('VDIUnavailable') 

1050 resp = {} 

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

1052 resp['sr_uuid'] = sr_uuid 

1053 resp['vdi_uuid'] = vdi_uuid 

1054 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

1059 

1060 @override 

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

1062 """ 

1063 Attach and activate a VDI using config generated by 

1064 vdi_generate_config above. This is used for cases such as 

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

1066 """ 

1067 util.SMlog("FileVDI.attach_from_config") 

1068 try: 

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

1070 return self.sr.attach(sr_uuid) 

1071 except: 

1072 util.logException("FileVDI.attach_from_config") 

1073 raise xs_errors.XenError( 

1074 'SRUnavailable', 

1075 opterr='Unable to attach from config' 

1076 ) 

1077 return '' 

1078 

1079 @override 

1080 def _create_cbt_log(self) -> str: 

1081 # Create CBT log file 

1082 # Name: <vdi_uuid>.cbtlog 

1083 #Handle if file already exists 

1084 log_path = self._get_cbt_logpath(self.uuid) 

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

1086 open_file.close() 

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

1088 

1089 @override 

1090 def _delete_cbt_log(self) -> None: 

1091 logPath = self._get_cbt_logpath(self.uuid) 

1092 try: 

1093 os.remove(logPath) 

1094 except OSError as e: 

1095 if e.errno != errno.ENOENT: 

1096 raise 

1097 

1098 @override 

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

1100 return util.pathexists(logpath) 

1101 

1102 

1103class SharedFileSR(FileSR): 

1104 """ 

1105 FileSR subclass for SRs that use shared network storage 

1106 """ 

1107 

1108 def _check_writable(self): 

1109 """ 

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

1111 raising an exception if it can't. 

1112 """ 

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

1114 try: 

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

1116 except OSError as e: 

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

1118 raise xs_errors.XenError('SharedFileSystemNoWrite') 

1119 finally: 

1120 util.force_unlink(test_name) 

1121 

1122 def _raise_hardlink_error(self): 

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

1124 

1125 @override 

1126 def _check_hardlinks(self) -> bool: 

1127 hardlink_conf = self._read_hardlink_conf() 

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

1129 return hardlink_conf 

1130 

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

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

1133 

1134 link_name = '%s.new' % test_name 

1135 try: 

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

1137 util.fistpoint.activate_custom_fn( 

1138 "FileSR_fail_hardlink", 

1139 self._raise_hardlink_error) 

1140 

1141 os.link(test_name, link_name) 

1142 self._write_hardlink_conf(supported=True) 

1143 return True 

1144 except OSError: 

1145 self._write_hardlink_conf(supported=False) 

1146 

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

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

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

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

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

1152 try: 

1153 self.session.xenapi.message.create( 

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

1155 msg) 

1156 except XenAPI.Failure: 

1157 # Might already be set and checking has TOCTOU issues 

1158 pass 

1159 finally: 

1160 util.force_unlink(link_name) 

1161 util.force_unlink(test_name) 

1162 

1163 return False 

1164 

1165 def _get_hardlink_conf_path(self): 

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

1167 

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

1169 try: 

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

1171 try: 

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

1173 except Exception as e: 

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

1175 return None 

1176 except IOError as e: 

1177 if e.errno == errno.ENOENT: 

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

1179 return None 

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

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

1182 return None 

1183 

1184 def _write_hardlink_conf(self, supported): 

1185 try: 

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

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

1188 except Exception as e: 

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

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

1191 

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

1193 SRCommand.run(FileSR, DRIVER_INFO) 

1194else: 

1195 SR.registerSR(FileSR)