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_preferred_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 VDI_TYPE = { 

444 PARAM_RAW: VdiType.RAW, 

445 PARAM_VHD: VdiType.VHD, 

446 PARAM_QCOW2: VdiType.QCOW2 

447 } 

448 

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

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

451 (vdi_uuid, self.PARAM_RAW)) 

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

453 (vdi_uuid, self.PARAM_VHD)) 

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

455 (vdi_uuid, self.PARAM_QCOW2)) 

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

457 (vdi_uuid, CBTLOG_TAG)) 

458 found = False 

459 tries = 0 

460 while tries < maxretry and not found: 

461 tries += 1 

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

463 self.vdi_type = VdiType.VHD 

464 self.path = vhd_path 

465 found = True 

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

467 self.vdi_type = VdiType.QCOW2 

468 self.path = qcow2_path 

469 found = True 

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

471 self.vdi_type = VdiType.RAW 

472 self.path = raw_path 

473 self.hidden = False 

474 found = True 

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

476 self.vdi_type = VdiType.CBTLOG 

477 self.path = cbt_path 

478 self.hidden = False 

479 found = True 

480 

481 if found: 

482 try: 

483 self.cowutil = getCowUtil(self.vdi_type) 

484 except: 

485 pass 

486 else: 

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

488 time.sleep(period) 

489 

490 return found 

491 

492 @override 

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

494 self.lock = self.sr.lock 

495 

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

497 

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

499 self.key_hash = None 

500 

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

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

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

504 

505 image_format = vdi_sm_config.get("image-format") or vdi_sm_config.get("type") 

506 if image_format: 506 ↛ 513line 506 didn't jump to line 513, because the condition on line 506 was never false

507 vdi_type = self.VDI_TYPE.get(image_format) 

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

509 raise xs_errors.XenError('VDIType', 

510 opterr='Invalid VDI type %s' % vdi_type) 

511 self.vdi_type = vdi_type 

512 

513 if not self.vdi_type: 513 ↛ 514line 513 didn't jump to line 514, because the condition on line 513 was never true

514 self.vdi_type = getVdiTypeFromImageFormat(self.sr.preferred_image_formats[0]) 

515 self.cowutil = getCowUtil(self.vdi_type) 

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

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

518 else: 

519 found = self._find_path_with_retries(vdi_uuid) 

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

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

522 # Could be delete for CBT log file 

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

524 return 

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

526 return 

527 raise xs_errors.XenError('VDIUnavailable', 

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

529 

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

531 if image_info: 

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

533 self.utilisation = image_info.sizePhys 

534 self.size = image_info.sizeVirt 

535 self.hidden = image_info.hidden 

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

537 self.managed = False 

538 self.parent = image_info.parentUuid 

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

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

541 else: 

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

543 return 

544 

545 try: 

546 # Change to the SR directory in case parent 

547 # locator field path has changed 

548 os.chdir(self.sr.path) 

549 except Exception as chdir_exception: 

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

551 str(chdir_exception)) 

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

553 

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

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

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

557 try: 

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

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

560 self.utilisation = int(st.st_size) 

561 except util.CommandException as inst: 

562 if inst.code == errno.EIO: 

563 raise xs_errors.XenError('VDILoad', \ 

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

565 else: 

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

567 self.path, str(inst))) 

568 raise xs_errors.XenError('VDIType', \ 

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

570 

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

572 self.exists = True 

573 self.size = self.utilisation 

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

575 return 

576 

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

578 self.exists = True 

579 self.size = self.utilisation 

580 return 

581 

582 try: 

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

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

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

586 

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

588 self.parent = image_info.parentUuid 

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

590 else: 

591 self.parent = "" 

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

593 self.size = image_info.sizeVirt 

594 self.hidden = image_info.hidden 

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

596 self.managed = False 

597 self.exists = True 

598 except util.CommandException as inst: 

599 raise xs_errors.XenError('VDILoad', \ 

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

601 

602 @override 

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

604 self.load(vdi_location) 

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

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

607 self._db_update() 

608 

609 @override 

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

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

612 raise xs_errors.XenError('VDIExists') 

613 

614 if VdiType.isCowImage(self.vdi_type): 

615 try: 

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

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

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

619 except util.CommandException as inst: 

620 raise xs_errors.XenError('VDICreate', 

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

622 else: 

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

624 f.truncate(int(size)) 

625 f.close() 

626 self.size = size 

627 

628 self.sr.added_vdi(self) 

629 

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

631 self.utilisation = int(st.st_size) 

632 if self.vdi_type == VdiType.RAW: 

633 # Legacy code. 

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

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

636 self.sm_config = {} 

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

638 

639 self._db_introduce() 

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

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

642 

643 @override 

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

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

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

647 

648 if self.attached: 

649 raise xs_errors.XenError('VDIInUse') 

650 

651 try: 

652 util.force_unlink(self.path) 

653 except Exception as e: 

654 raise xs_errors.XenError( 

655 'VDIDelete', 

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

657 

658 self.sr.deleted_vdi(vdi_uuid) 

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

660 if not data_only: 

661 self._db_forget() 

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

663 self.sr.lock.cleanupAll(vdi_uuid) 

664 self.sr._kickGC() 

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

666 

667 @override 

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

669 if self.path is None: 

670 self._find_path_with_retries(vdi_uuid) 

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

672 raise xs_errors.XenError('VDIUnavailable', \ 

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

674 try: 

675 self.attached = True 

676 

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

678 self.xenstore_data = {} 

679 

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

681 scsiutil.gen_synthetic_page_data(vdi_uuid))) 

682 

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

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

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

686 # pool database. 

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

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

689 except util.CommandException as inst: 

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

691 

692 @override 

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

694 self.attached = False 

695 

696 @override 

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

698 if not self.exists: 

699 raise xs_errors.XenError('VDIUnavailable', \ 

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

701 

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

703 raise xs_errors.XenError('Unimplemented') 

704 

705 if self.hidden: 

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

707 

708 if size < self.size: 

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

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

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

712 

713 if size == self.size: 

714 return VDI.VDI.get_params(self) 

715 

716 # We already checked it is a cow image. 

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

718 

719 jFile = JOURNAL_FILE_PREFIX + self.uuid 

720 try: 

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

722 except: 

723 # Revert the operation 

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

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

726 

727 old_size = self.size 

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

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

730 self.utilisation = int(st.st_size) 

731 

732 self._db_update() 

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

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

735 return VDI.VDI.get_params(self) 

736 

737 @override 

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

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

740 

741 @override 

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

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

744 raise xs_errors.XenError('Unimplemented') 

745 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

747 assert(util.pathexists(parent_path)) 

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

749 self.cowutil.setHidden(parent_path) 

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

751 # Tell tapdisk the chain has changed 

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

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

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

755 

756 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

758 raise xs_errors.XenError('Unimplemented') 

759 

760 # safety check 

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

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

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

764 

765 self.cowutil.killData(self.path) 

766 

767 @override 

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

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

770 # If cbt enabled, save file consistency state 

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

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

773 consistency_state = False 

774 else: 

775 consistency_state = True 

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

777 (consistency_state, vdi_uuid)) 

778 else: 

779 consistency_state = None 

780 

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

782 raise xs_errors.XenError('Unimplemented') 

783 

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

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

786 try: 

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

788 finally: 

789 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

791 

792 @override 

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

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

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

796 

797 def _link(self, src, dst): 

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

799 os.link(src, dst) 

800 

801 def _unlink(self, path): 

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

803 os.unlink(path) 

804 

805 def _create_new_parent(self, src, newsrc): 

806 if self.sr._check_hardlinks(): 

807 self._link(src, newsrc) 

808 else: 

809 self._rename(src, newsrc) 

810 

811 def __fist_enospace(self): 

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

813 

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

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

816 

817 args = [] 

818 args.append("vdi_clone") 

819 args.append(self.sr.uuid) 

820 args.append(self.uuid) 

821 

822 dest = None 

823 dst = None 

824 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

826 dest = util.gen_uuid() 

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

828 args.append(dest) 

829 

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

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

832 

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

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

835 raise xs_errors.XenError('VDIUnavailable', \ 

836 opterr='failed to get image depth') 

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

838 raise xs_errors.XenError('SnapshotChainTooLong') 

839 

840 newuuid = util.gen_uuid() 

841 src = self.path 

842 newsrcname = newuuid + extension 

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

844 

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

846 raise xs_errors.XenError('VDIUnavailable', \ 

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

848 

849 # wkcfix: multiphase 

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

851 

852 # We assume the filehandle has been released 

853 try: 

854 self._create_new_parent(src, newsrc) 

855 

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

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

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

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

860 # before so nobody will try to query it. 

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

862 # Fault injection site to fail the snapshot with ENOSPACE 

863 util.fistpoint.activate_custom_fn( 

864 "FileSR_fail_snap1", 

865 self.__fist_enospace) 

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

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

868 # hardlink leaf too quickly after creating it. 

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

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

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

872 # Fault injection site to fail the snapshot with ENOSPACE 

873 util.fistpoint.activate_custom_fn( 

874 "FileSR_fail_snap2", 

875 self.__fist_enospace) 

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

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

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

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

880 

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

882 introduce_parent = True 

883 try: 

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

885 dstparent = None 

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

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

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

889 (snap_type == VDI.SNAPSHOT_SINGLE or \ 

890 snap_type == VDI.SNAPSHOT_INTERNAL or \ 

891 dstparent != newuuid): 

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

893 introduce_parent = False 

894 except Exception as e: 

895 raise 

896 

897 # Introduce the new VDI records 

898 leaf_vdi = None 

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

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

901 leaf_vdi.read_only = False 

902 leaf_vdi.location = dest 

903 leaf_vdi.size = self.size 

904 leaf_vdi.utilisation = self.utilisation 

905 leaf_vdi.sm_config = {} 

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

907 # TODO: fix the raw snapshot case  

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

909 # If the parent is encrypted set the key_hash 

910 # for the new snapshot disk 

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

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

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

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

915 # If we have CBT enabled on the VDI, 

916 # set CBT status for the new snapshot disk 

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

918 leaf_vdi.cbt_enabled = True 

919 

920 base_vdi = None 

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

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

923 base_vdi.label = "base copy" 

924 base_vdi.read_only = True 

925 base_vdi.location = newuuid 

926 base_vdi.size = self.size 

927 base_vdi.utilisation = self.utilisation 

928 base_vdi.sm_config = {} 

929 # TODO: fix the raw snapshot case  

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

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

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

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

934 

935 try: 

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

937 leaf_vdi_ref = leaf_vdi._db_introduce() 

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

939 (leaf_vdi_ref, dest)) 

940 

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

942 base_vdi_ref = base_vdi._db_introduce() 

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

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

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

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

947 sm_config['vhd-parent'] = srcparent 

948 # TODO: fix the raw snapshot case  

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

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

951 except Exception as e: 

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

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

954 # been marked as deleted already. 

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

956 raise 

957 except util.CommandException as inst: 

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

959 self._clonecleanup(src, dst, newsrc) 

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

961 raise xs_errors.XenError('VDIClone', 

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

963 

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

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

966 try: 

967 self._cbt_snapshot(dest, cbt_consistency) 

968 except: 

969 # CBT operation failed. 

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

971 raise 

972 

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

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

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

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

977 ret_vdi = leaf_vdi 

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

979 ret_vdi = base_vdi 

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

981 ret_vdi = self 

982 return ret_vdi.get_params() 

983 

984 @override 

985 def get_params(self) -> str: 

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

987 raise xs_errors.XenError('VDIUnavailable', \ 

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

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

990 

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

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

993 

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

995 try: 

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

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

998 except util.CommandException as inst: 

999 pass 

1000 try: 

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

1002 stats = os.stat(newsrc) 

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

1004 if (stats.st_nlink > 1): 

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

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

1007 self._rename(newsrc, src) 

1008 except util.CommandException as inst: 

1009 pass 

1010 

1011 def _checkpath(self, path): 

1012 try: 

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

1014 return False 

1015 return True 

1016 except util.CommandException as inst: 

1017 raise xs_errors.XenError('EIO', \ 

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

1019 

1020 def _create(self, size, path): 

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

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

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

1024 

1025 def _mark_hidden(self, path): 

1026 self.cowutil.setHidden(path, True) 

1027 self.hidden = 1 

1028 

1029 def _is_hidden(self, path): 

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

1031 

1032 @staticmethod 

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

1034 fileName = os.path.basename(path) 

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

1036 

1037 @override 

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

1039 """ 

1040 Generate the XML config required to attach and activate 

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

1042 activation is handled by vdi_attach_from_config below. 

1043 """ 

1044 util.SMlog("FileVDI.generate_config") 

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

1046 raise xs_errors.XenError('VDIUnavailable') 

1047 resp = {} 

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

1049 resp['sr_uuid'] = sr_uuid 

1050 resp['vdi_uuid'] = vdi_uuid 

1051 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

1056 

1057 @override 

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

1059 """ 

1060 Attach and activate a VDI using config generated by 

1061 vdi_generate_config above. This is used for cases such as 

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

1063 """ 

1064 util.SMlog("FileVDI.attach_from_config") 

1065 try: 

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

1067 return self.sr.attach(sr_uuid) 

1068 except: 

1069 util.logException("FileVDI.attach_from_config") 

1070 raise xs_errors.XenError( 

1071 'SRUnavailable', 

1072 opterr='Unable to attach from config' 

1073 ) 

1074 return '' 

1075 

1076 @override 

1077 def _create_cbt_log(self) -> str: 

1078 # Create CBT log file 

1079 # Name: <vdi_uuid>.cbtlog 

1080 #Handle if file already exists 

1081 log_path = self._get_cbt_logpath(self.uuid) 

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

1083 open_file.close() 

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

1085 

1086 @override 

1087 def _delete_cbt_log(self) -> None: 

1088 logPath = self._get_cbt_logpath(self.uuid) 

1089 try: 

1090 os.remove(logPath) 

1091 except OSError as e: 

1092 if e.errno != errno.ENOENT: 

1093 raise 

1094 

1095 @override 

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

1097 return util.pathexists(logpath) 

1098 

1099 

1100class SharedFileSR(FileSR): 

1101 """ 

1102 FileSR subclass for SRs that use shared network storage 

1103 """ 

1104 

1105 def _check_writable(self): 

1106 """ 

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

1108 raising an exception if it can't. 

1109 """ 

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

1111 try: 

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

1113 except OSError as e: 

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

1115 raise xs_errors.XenError('SharedFileSystemNoWrite') 

1116 finally: 

1117 util.force_unlink(test_name) 

1118 

1119 def _raise_hardlink_error(self): 

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

1121 

1122 @override 

1123 def _check_hardlinks(self) -> bool: 

1124 hardlink_conf = self._read_hardlink_conf() 

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

1126 return hardlink_conf 

1127 

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

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

1130 

1131 link_name = '%s.new' % test_name 

1132 try: 

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

1134 util.fistpoint.activate_custom_fn( 

1135 "FileSR_fail_hardlink", 

1136 self._raise_hardlink_error) 

1137 

1138 os.link(test_name, link_name) 

1139 self._write_hardlink_conf(supported=True) 

1140 return True 

1141 except OSError: 

1142 self._write_hardlink_conf(supported=False) 

1143 

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

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

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

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

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

1149 try: 

1150 self.session.xenapi.message.create( 

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

1152 msg) 

1153 except XenAPI.Failure: 

1154 # Might already be set and checking has TOCTOU issues 

1155 pass 

1156 finally: 

1157 util.force_unlink(link_name) 

1158 util.force_unlink(test_name) 

1159 

1160 return False 

1161 

1162 def _get_hardlink_conf_path(self): 

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

1164 

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

1166 try: 

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

1168 try: 

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

1170 except Exception as e: 

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

1172 return None 

1173 except IOError as e: 

1174 if e.errno == errno.ENOENT: 

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

1176 return None 

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

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

1179 return None 

1180 

1181 def _write_hardlink_conf(self, supported): 

1182 try: 

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

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

1185 except Exception as e: 

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

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

1188 

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

1190 SRCommand.run(FileSR, DRIVER_INFO) 

1191else: 

1192 SR.registerSR(FileSR)