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 size = int(self.sr.srcmd.params['args'][0]) 

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

516 # We need it to validate the vdi_type choice 

517 for image_format in self.sr.preferred_image_formats: 

518 vdi_type = getVdiTypeFromImageFormat(image_format) 

519 cowutil = getCowUtil(vdi_type) 

520 try: 

521 cowutil.validateAndRoundImageSize(size) 

522 break 

523 except xs_errors.SROSError: 

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

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

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

527 self.vdi_type = vdi_type 

528 self.cowutil = cowutil 

529 else: 

530 self.cowutil = getCowUtil(self.vdi_type) 

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

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

533 else: 

534 found = self._find_path_with_retries(vdi_uuid) 

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

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

537 # Could be delete for CBT log file 

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

539 return 

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

541 return 

542 raise xs_errors.XenError('VDIUnavailable', 

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

544 

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

546 if image_info: 

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

548 self.utilisation = image_info.sizePhys 

549 self.size = image_info.sizeVirt 

550 self.hidden = image_info.hidden 

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

552 self.managed = False 

553 self.parent = image_info.parentUuid 

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

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

556 else: 

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

558 return 

559 

560 try: 

561 # Change to the SR directory in case parent 

562 # locator field path has changed 

563 os.chdir(self.sr.path) 

564 except Exception as chdir_exception: 

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

566 str(chdir_exception)) 

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

568 

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

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

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

572 try: 

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

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

575 self.utilisation = int(st.st_size) 

576 except util.CommandException as inst: 

577 if inst.code == errno.EIO: 

578 raise xs_errors.XenError('VDILoad', \ 

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

580 else: 

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

582 self.path, str(inst))) 

583 raise xs_errors.XenError('VDIType', \ 

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

585 

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

587 self.exists = True 

588 self.size = self.utilisation 

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

590 return 

591 

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

593 self.exists = True 

594 self.size = self.utilisation 

595 return 

596 

597 try: 

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

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

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

601 

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

603 self.parent = image_info.parentUuid 

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

605 else: 

606 self.parent = "" 

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

608 self.size = image_info.sizeVirt 

609 self.hidden = image_info.hidden 

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

611 self.managed = False 

612 self.exists = True 

613 except util.CommandException as inst: 

614 raise xs_errors.XenError('VDILoad', \ 

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

616 

617 @override 

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

619 self.load(vdi_location) 

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

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

622 self._db_update() 

623 

624 @override 

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

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

627 raise xs_errors.XenError('VDIExists') 

628 

629 if VdiType.isCowImage(self.vdi_type): 

630 try: 

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

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

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

634 except util.CommandException as inst: 

635 raise xs_errors.XenError('VDICreate', 

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

637 else: 

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

639 f.truncate(int(size)) 

640 f.close() 

641 self.size = size 

642 

643 self.sr.added_vdi(self) 

644 

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

646 self.utilisation = int(st.st_size) 

647 if self.vdi_type == VdiType.RAW: 

648 # Legacy code. 

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

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

651 self.sm_config = {} 

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

653 

654 self._db_introduce() 

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

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

657 

658 @override 

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

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

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

662 

663 if self.attached: 

664 raise xs_errors.XenError('VDIInUse') 

665 

666 try: 

667 util.force_unlink(self.path) 

668 except Exception as e: 

669 raise xs_errors.XenError( 

670 'VDIDelete', 

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

672 

673 self.sr.deleted_vdi(vdi_uuid) 

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

675 if not data_only: 

676 self._db_forget() 

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

678 self.sr.lock.cleanupAll(vdi_uuid) 

679 self.sr._kickGC() 

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

681 

682 @override 

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

684 if self.path is None: 

685 self._find_path_with_retries(vdi_uuid) 

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

687 raise xs_errors.XenError('VDIUnavailable', \ 

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

689 try: 

690 self.attached = True 

691 

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

693 self.xenstore_data = {} 

694 

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

696 scsiutil.gen_synthetic_page_data(vdi_uuid))) 

697 

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

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

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

701 # pool database. 

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

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

704 except util.CommandException as inst: 

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

706 

707 @override 

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

709 self.attached = False 

710 

711 @override 

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

713 if not self.exists: 

714 raise xs_errors.XenError('VDIUnavailable', \ 

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

716 

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

718 raise xs_errors.XenError('Unimplemented') 

719 

720 if self.hidden: 

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

722 

723 if size < self.size: 

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

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

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

727 

728 if size == self.size: 

729 return VDI.VDI.get_params(self) 

730 

731 # We already checked it is a cow image. 

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

733 

734 jFile = JOURNAL_FILE_PREFIX + self.uuid 

735 try: 

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

737 except: 

738 # Revert the operation 

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

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

741 

742 old_size = self.size 

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

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

745 self.utilisation = int(st.st_size) 

746 

747 self._db_update() 

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

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

750 return VDI.VDI.get_params(self) 

751 

752 @override 

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

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

755 

756 @override 

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

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

759 raise xs_errors.XenError('Unimplemented') 

760 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

762 assert(util.pathexists(parent_path)) 

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

764 self.cowutil.setHidden(parent_path) 

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

766 # Tell tapdisk the chain has changed 

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

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

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

770 

771 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

773 raise xs_errors.XenError('Unimplemented') 

774 

775 # safety check 

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

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

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

779 

780 self.cowutil.killData(self.path) 

781 

782 @override 

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

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

785 # If cbt enabled, save file consistency state 

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

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

788 consistency_state = False 

789 else: 

790 consistency_state = True 

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

792 (consistency_state, vdi_uuid)) 

793 else: 

794 consistency_state = None 

795 

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

797 raise xs_errors.XenError('Unimplemented') 

798 

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

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

801 try: 

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

803 finally: 

804 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

806 

807 @override 

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

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

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

811 

812 def _link(self, src, dst): 

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

814 os.link(src, dst) 

815 

816 def _unlink(self, path): 

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

818 os.unlink(path) 

819 

820 def _create_new_parent(self, src, newsrc): 

821 if self.sr._check_hardlinks(): 

822 self._link(src, newsrc) 

823 else: 

824 self._rename(src, newsrc) 

825 

826 def __fist_enospace(self): 

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

828 

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

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

831 

832 args = [] 

833 args.append("vdi_clone") 

834 args.append(self.sr.uuid) 

835 args.append(self.uuid) 

836 

837 dest = None 

838 dst = None 

839 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

841 dest = util.gen_uuid() 

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

843 args.append(dest) 

844 

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

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

847 

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

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

850 raise xs_errors.XenError('VDIUnavailable', \ 

851 opterr='failed to get image depth') 

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

853 raise xs_errors.XenError('SnapshotChainTooLong') 

854 

855 newuuid = util.gen_uuid() 

856 src = self.path 

857 newsrcname = newuuid + extension 

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

859 

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

861 raise xs_errors.XenError('VDIUnavailable', \ 

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

863 

864 # wkcfix: multiphase 

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

866 

867 # We assume the filehandle has been released 

868 try: 

869 self._create_new_parent(src, newsrc) 

870 

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

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

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

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

875 # before so nobody will try to query it. 

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

877 # Fault injection site to fail the snapshot with ENOSPACE 

878 util.fistpoint.activate_custom_fn( 

879 "FileSR_fail_snap1", 

880 self.__fist_enospace) 

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

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

883 # hardlink leaf too quickly after creating it. 

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

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

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

887 # Fault injection site to fail the snapshot with ENOSPACE 

888 util.fistpoint.activate_custom_fn( 

889 "FileSR_fail_snap2", 

890 self.__fist_enospace) 

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

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

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

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

895 

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

897 introduce_parent = True 

898 try: 

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

900 dstparent = None 

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

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

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

904 (snap_type == VDI.SNAPSHOT_SINGLE or \ 

905 snap_type == VDI.SNAPSHOT_INTERNAL or \ 

906 dstparent != newuuid): 

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

908 introduce_parent = False 

909 except Exception as e: 

910 raise 

911 

912 # Introduce the new VDI records 

913 leaf_vdi = None 

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

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

916 leaf_vdi.read_only = False 

917 leaf_vdi.location = dest 

918 leaf_vdi.size = self.size 

919 leaf_vdi.utilisation = self.utilisation 

920 leaf_vdi.sm_config = {} 

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

922 # TODO: fix the raw snapshot case  

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

924 # If the parent is encrypted set the key_hash 

925 # for the new snapshot disk 

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

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

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

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

930 # If we have CBT enabled on the VDI, 

931 # set CBT status for the new snapshot disk 

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

933 leaf_vdi.cbt_enabled = True 

934 

935 base_vdi = None 

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

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

938 base_vdi.label = "base copy" 

939 base_vdi.read_only = True 

940 base_vdi.location = newuuid 

941 base_vdi.size = self.size 

942 base_vdi.utilisation = self.utilisation 

943 base_vdi.sm_config = {} 

944 # TODO: fix the raw snapshot case  

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

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

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

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

949 

950 try: 

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

952 leaf_vdi_ref = leaf_vdi._db_introduce() 

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

954 (leaf_vdi_ref, dest)) 

955 

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

957 base_vdi_ref = base_vdi._db_introduce() 

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

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

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

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

962 sm_config['vhd-parent'] = srcparent 

963 # TODO: fix the raw snapshot case  

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

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

966 except Exception as e: 

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

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

969 # been marked as deleted already. 

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

971 raise 

972 except util.CommandException as inst: 

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

974 self._clonecleanup(src, dst, newsrc) 

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

976 raise xs_errors.XenError('VDIClone', 

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

978 

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

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

981 try: 

982 self._cbt_snapshot(dest, cbt_consistency) 

983 except: 

984 # CBT operation failed. 

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

986 raise 

987 

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

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

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

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

992 ret_vdi = leaf_vdi 

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

994 ret_vdi = base_vdi 

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

996 ret_vdi = self 

997 return ret_vdi.get_params() 

998 

999 @override 

1000 def get_params(self) -> str: 

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

1002 raise xs_errors.XenError('VDIUnavailable', \ 

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

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

1005 

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

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

1008 

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

1010 try: 

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

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

1013 except util.CommandException as inst: 

1014 pass 

1015 try: 

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

1017 stats = os.stat(newsrc) 

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

1019 if (stats.st_nlink > 1): 

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

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

1022 self._rename(newsrc, src) 

1023 except util.CommandException as inst: 

1024 pass 

1025 

1026 def _checkpath(self, path): 

1027 try: 

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

1029 return False 

1030 return True 

1031 except util.CommandException as inst: 

1032 raise xs_errors.XenError('EIO', \ 

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

1034 

1035 def _create(self, size, path): 

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

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

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

1039 

1040 def _mark_hidden(self, path): 

1041 self.cowutil.setHidden(path, True) 

1042 self.hidden = 1 

1043 

1044 def _is_hidden(self, path): 

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

1046 

1047 @staticmethod 

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

1049 fileName = os.path.basename(path) 

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

1051 

1052 @override 

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

1054 """ 

1055 Generate the XML config required to attach and activate 

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

1057 activation is handled by vdi_attach_from_config below. 

1058 """ 

1059 util.SMlog("FileVDI.generate_config") 

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

1061 raise xs_errors.XenError('VDIUnavailable') 

1062 resp = {} 

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

1064 resp['sr_uuid'] = sr_uuid 

1065 resp['vdi_uuid'] = vdi_uuid 

1066 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

1071 

1072 @override 

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

1074 """ 

1075 Attach and activate a VDI using config generated by 

1076 vdi_generate_config above. This is used for cases such as 

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

1078 """ 

1079 util.SMlog("FileVDI.attach_from_config") 

1080 try: 

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

1082 return self.sr.attach(sr_uuid) 

1083 except: 

1084 util.logException("FileVDI.attach_from_config") 

1085 raise xs_errors.XenError( 

1086 'SRUnavailable', 

1087 opterr='Unable to attach from config' 

1088 ) 

1089 return '' 

1090 

1091 @override 

1092 def _create_cbt_log(self) -> str: 

1093 # Create CBT log file 

1094 # Name: <vdi_uuid>.cbtlog 

1095 #Handle if file already exists 

1096 log_path = self._get_cbt_logpath(self.uuid) 

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

1098 open_file.close() 

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

1100 

1101 @override 

1102 def _delete_cbt_log(self) -> None: 

1103 logPath = self._get_cbt_logpath(self.uuid) 

1104 try: 

1105 os.remove(logPath) 

1106 except OSError as e: 

1107 if e.errno != errno.ENOENT: 

1108 raise 

1109 

1110 @override 

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

1112 return util.pathexists(logpath) 

1113 

1114 

1115class SharedFileSR(FileSR): 

1116 """ 

1117 FileSR subclass for SRs that use shared network storage 

1118 """ 

1119 

1120 def _check_writable(self): 

1121 """ 

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

1123 raising an exception if it can't. 

1124 """ 

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

1126 try: 

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

1128 except OSError as e: 

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

1130 raise xs_errors.XenError('SharedFileSystemNoWrite') 

1131 finally: 

1132 util.force_unlink(test_name) 

1133 

1134 def _raise_hardlink_error(self): 

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

1136 

1137 @override 

1138 def _check_hardlinks(self) -> bool: 

1139 hardlink_conf = self._read_hardlink_conf() 

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

1141 return hardlink_conf 

1142 

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

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

1145 

1146 link_name = '%s.new' % test_name 

1147 try: 

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

1149 util.fistpoint.activate_custom_fn( 

1150 "FileSR_fail_hardlink", 

1151 self._raise_hardlink_error) 

1152 

1153 os.link(test_name, link_name) 

1154 self._write_hardlink_conf(supported=True) 

1155 return True 

1156 except OSError: 

1157 self._write_hardlink_conf(supported=False) 

1158 

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

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

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

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

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

1164 try: 

1165 self.session.xenapi.message.create( 

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

1167 msg) 

1168 except XenAPI.Failure: 

1169 # Might already be set and checking has TOCTOU issues 

1170 pass 

1171 finally: 

1172 util.force_unlink(link_name) 

1173 util.force_unlink(test_name) 

1174 

1175 return False 

1176 

1177 def _get_hardlink_conf_path(self): 

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

1179 

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

1181 try: 

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

1183 try: 

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

1185 except Exception as e: 

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

1187 return None 

1188 except IOError as e: 

1189 if e.errno == errno.ENOENT: 

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

1191 return None 

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

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

1194 return None 

1195 

1196 def _write_hardlink_conf(self, supported): 

1197 try: 

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

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

1200 except Exception as e: 

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

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

1203 

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

1205 SRCommand.run(FileSR, DRIVER_INFO) 

1206else: 

1207 SR.registerSR(FileSR)