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 \ 

37 CowImageInfo, CowUtil, ImageFormat, getCowUtil, getImageStringFromVdiType, getVdiTypeFromImageFormat 

38from vditype import VdiType, VdiTypeExtension, VDI_COW_TYPES, VDI_TYPE_TO_EXTENSION 

39import xmlrpc.client 

40import XenAPI # pylint: disable=import-error 

41from constants import CBTLOG_TAG 

42 

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

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

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

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

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

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

49 

50CONFIGURATION = [ 

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

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

53] 

54 

55DRIVER_INFO = { 

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

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

58 'vendor': 'Citrix Systems Inc', 

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

60 'driver_version': '1.0', 

61 'required_api_version': '1.0', 

62 'capabilities': CAPABILITIES, 

63 'configuration': CONFIGURATION 

64 } 

65 

66JOURNAL_FILE_PREFIX = ".journal-" 

67 

68OPS_EXCLUSIVE = [ 

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

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

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

72 

73DRIVER_CONFIG = {"ATTACH_FROM_CONFIG_WITH_TAPDISK": True} 

74 

75 

76class FileSR(SR.SR): 

77 """Local file storage repository""" 

78 

79 SR_TYPE = "file" 

80 

81 @override 

82 @staticmethod 

83 def handles(srtype) -> bool: 

84 return srtype == 'file' 

85 

86 def _check_o_direct(self): 

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

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

89 o_direct = other_config.get("o_direct") 

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

91 else: 

92 self.o_direct = True 

93 

94 def __init__(self, srcmd, sr_uuid): 

95 # We call SR.SR.__init__ explicitly because 

96 # "super" sometimes failed due to circular imports 

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

98 self.image_info = {} 

99 self._init_preferred_image_formats() 

100 self._check_o_direct() 

101 

102 @override 

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

104 self.ops_exclusive = OPS_EXCLUSIVE 

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

106 self.sr_vditype = SR.DEFAULT_TAP 

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

108 raise xs_errors.XenError('ConfigLocationMissing') 

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

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

111 self.linkpath = self.path 

112 self.mountpoint = self.path 

113 self.attached = False 

114 self.driver_config = DRIVER_CONFIG 

115 

116 @override 

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

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

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

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

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

122 try: 

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

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

125 raise xs_errors.XenError('SRExists') 

126 else: 

127 try: 

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

129 except util.CommandException as inst: 

130 if inst.code == errno.EEXIST: 

131 raise xs_errors.XenError('SRExists') 

132 else: 

133 raise xs_errors.XenError('FileSRCreate', \ 

134 opterr='directory creation failure %d' \ 

135 % inst.code) 

136 except: 

137 raise xs_errors.XenError('FileSRCreate') 

138 

139 @override 

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

141 self.attach(sr_uuid) 

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

143 

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

145 # files that are non VDI's 

146 try: 

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

148 #Load the VDI list 

149 self._loadvdis() 

150 for uuid in self.vdis: 

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

152 raise xs_errors.XenError('SRNotEmpty', \ 

153 opterr='VDIs still exist in SR') 

154 

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

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

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

158 try: 

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

160 except util.CommandException as inst: 

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

162 inst.code != errno.EISDIR: 

163 raise xs_errors.XenError('FileSRDelete', \ 

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

165 % (fullpath, inst.code)) 

166 self.detach(sr_uuid) 

167 except util.CommandException as inst: 

168 self.detach(sr_uuid) 

169 raise xs_errors.XenError('FileSRDelete', \ 

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

171 

172 @override 

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

174 self.attach_and_bind(sr_uuid) 

175 

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

177 if not self._checkmount(): 

178 try: 

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

180 except util.CommandException as inst: 

181 if inst.code != errno.EEXIST: 

182 raise xs_errors.XenError("FileSRCreate", \ 

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

184 try: 

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

186 if bind: 

187 cmd.append("--bind") 

188 util.pread(cmd) 

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

190 except util.CommandException as inst: 

191 raise xs_errors.XenError('FileSRCreate', \ 

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

193 self.attached = True 

194 

195 @override 

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

197 if self._checkmount(): 

198 try: 

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

200 cleanup.abort(self.uuid) 

201 os.chdir(SR.MOUNT_BASE) 

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

203 os.rmdir(self.path) 

204 except Exception as e: 

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

206 self.attached = False 

207 

208 @override 

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

210 if not self._checkmount(): 

211 raise xs_errors.XenError('SRUnavailable', \ 

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

213 

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

215 self._loadvdis() 

216 

217 if not self.passthrough: 

218 self.physical_size = self._getsize() 

219 self.physical_utilisation = self._getutilisation() 

220 

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

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

223 del self.vdis[uuid] 

224 

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

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

227 # which would be a shame. 

228 # For SMB SRs, this path is mountpoint 

229 mount_path = self.path 

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

231 mount_path = self.mountpoint 

232 

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

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

235 raise xs_errors.XenError('SRUnavailable', \ 

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

237 

238 self._kickGC() 

239 

240 # default behaviour from here on 

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

242 

243 @override 

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

245 if not self._checkmount(): 

246 raise xs_errors.XenError('SRUnavailable', \ 

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

248 self._update(sr_uuid, 0) 

249 

250 def _update(self, sr_uuid, virt_alloc_delta): 

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

252 self.virtual_allocation = valloc + virt_alloc_delta 

253 self.physical_size = self._getsize() 

254 self.physical_utilisation = self._getutilisation() 

255 self._db_update() 

256 

257 @override 

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

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

260 

261 @override 

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

263 return FileVDI(self, uuid) 

264 

265 def added_vdi(self, vdi): 

266 self.vdis[vdi.uuid] = vdi 

267 

268 def deleted_vdi(self, uuid): 

269 if uuid in self.vdis: 

270 del self.vdis[uuid] 

271 

272 @override 

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

274 try: 

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

276 data = file.readlines() 

277 file.close() 

278 self._process_replay(data) 

279 except: 

280 raise xs_errors.XenError('SRLog') 

281 

282 def _loadvdis(self): 

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

284 return 

285 

286 self.image_info = {} 

287 for vdi_type in VDI_COW_TYPES: 

288 extension = VDI_TYPE_TO_EXTENSION[vdi_type] 

289 

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

291 info = {} 

292 

293 cowutil = getCowUtil(vdi_type) 

294 try: 

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

296 except util.CommandException as inst: 

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

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

299 try: 

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

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

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

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

304 except: 

305 pass 

306 

307 self.image_info.update(info) 

308 

309 for uuid in self.image_info.keys(): 

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

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

312 

313 file_vdi = self.vdi(uuid) 

314 file_vdi.cowutil = cowutil 

315 self.vdis[uuid] = file_vdi 

316 

317 # Get the key hash of any encrypted VDIs: 

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

319 key_hash = cowutil.getKeyHash(vdi_path) 

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

321 

322 # raw VDIs and CBT log files 

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

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

325 if fn.endswith(VdiTypeExtension.RAW): 

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

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

328 elif fn.endswith(CBTLOG_TAG): 

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

330 # If an associated disk exists, update CBT status 

331 # else create new VDI of type cbt_metadata 

332 if cbt_uuid in self.vdis: 

333 self.vdis[cbt_uuid].cbt_enabled = True 

334 else: 

335 new_vdi = self.vdi(cbt_uuid) 

336 new_vdi.ty = "cbt_metadata" 

337 new_vdi.cbt_enabled = True 

338 self.vdis[cbt_uuid] = new_vdi 

339 

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

341 self.virtual_allocation = 0 

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

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

344 if vdi.parent in self.vdis: 

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

346 if vdi.parent in geneology: 

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

348 else: 

349 geneology[vdi.parent] = [uuid] 

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

351 self.virtual_allocation += (vdi.size) 

352 

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

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

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

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

357 # freshly-deleted VDIs as newly-added VDIs 

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

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

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

361 del self.vdis[uuid] 

362 

363 def _getsize(self): 

364 path = self.path 

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

366 path = self.linkpath 

367 return util.get_fs_size(path) 

368 

369 def _getutilisation(self): 

370 return util.get_fs_utilisation(self.path) 

371 

372 def _replay(self, logentry): 

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

374 # vdi_command, sr-uuid, vdi-uuid 

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

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

377 cmd = getattr(target, back_cmd) 

378 args = [] 

379 for item in logentry[6:]: 

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

381 args.append(item) 

382 ret = cmd( * args) 

383 if ret: 

384 print(ret) 

385 

386 def _compare_args(self, a, b): 

387 try: 

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

389 return 1 

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

391 return 1 

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

393 return 1 

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

395 return 1 

396 return 0 

397 except: 

398 return 1 

399 

400 def _process_replay(self, data): 

401 logentries = [] 

402 for logentry in data: 

403 logentry = logentry.split(" ") 

404 logentries.append(logentry) 

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

406 # wkcfix -- recreate (adjusted) logfile 

407 index = 0 

408 while index < len(logentries) - 1: 

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

410 self._replay(logentries[index]) 

411 else: 

412 # skip the paired one 

413 index += 1 

414 # next 

415 index += 1 

416 

417 def _kickGC(self): 

418 util.SMlog("Kicking GC") 

419 cleanup.start_gc_service(self.uuid) 

420 

421 def _isbind(self): 

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

423 st1 = os.stat(self.path) 

424 st2 = os.stat(self.remotepath) 

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

426 

427 def _checkmount(self) -> bool: 

428 mount_path = self.path 

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

430 mount_path = self.mountpoint 

431 

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

433 (util.ismount(mount_path) or \ 

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

435 

436 # Override in SharedFileSR. 

437 def _check_hardlinks(self) -> bool: 

438 return True 

439 

440class FileVDI(VDI.VDI): 

441 PARAM_RAW = "raw" 

442 PARAM_VHD = "vhd" 

443 PARAM_QCOW2 = "qcow2" 

444 VDI_TYPE = { 

445 PARAM_RAW: VdiType.RAW, 

446 PARAM_VHD: VdiType.VHD, 

447 PARAM_QCOW2: VdiType.QCOW2 

448 } 

449 

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

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

452 (vdi_uuid, self.PARAM_RAW)) 

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

454 (vdi_uuid, self.PARAM_VHD)) 

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

456 (vdi_uuid, self.PARAM_QCOW2)) 

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

458 (vdi_uuid, CBTLOG_TAG)) 

459 found = False 

460 tries = 0 

461 while tries < maxretry and not found: 

462 tries += 1 

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

464 self.vdi_type = VdiType.VHD 

465 self.path = vhd_path 

466 found = True 

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

468 self.vdi_type = VdiType.QCOW2 

469 self.path = qcow2_path 

470 found = True 

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

472 self.vdi_type = VdiType.RAW 

473 self.path = raw_path 

474 self.hidden = False 

475 found = True 

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

477 self.vdi_type = VdiType.CBTLOG 

478 self.path = cbt_path 

479 self.hidden = False 

480 found = True 

481 

482 if found: 

483 try: 

484 self.cowutil = getCowUtil(self.vdi_type) 

485 except: 

486 pass 

487 else: 

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

489 time.sleep(period) 

490 

491 return found 

492 

493 @override 

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

495 self.lock = self.sr.lock 

496 

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

498 

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

500 self.key_hash = None 

501 

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

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

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

505 

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

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

508 vdi_type = self.VDI_TYPE.get(image_format) 

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

510 raise xs_errors.XenError('VDIType', 

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

512 self.vdi_type = vdi_type 

513 

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

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

516 self.cowutil = getCowUtil(self.vdi_type) 

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

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

519 else: 

520 found = self._find_path_with_retries(vdi_uuid) 

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

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

523 # Could be delete for CBT log file 

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

525 return 

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

527 return 

528 raise xs_errors.XenError('VDIUnavailable', 

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

530 

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

532 if image_info: 

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

534 self.utilisation = image_info.sizePhys 

535 self.size = image_info.sizeVirt 

536 self.hidden = image_info.hidden 

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

538 self.managed = False 

539 self.parent = image_info.parentUuid 

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

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

542 else: 

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

544 return 

545 

546 try: 

547 # Change to the SR directory in case parent 

548 # locator field path has changed 

549 os.chdir(self.sr.path) 

550 except Exception as chdir_exception: 

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

552 str(chdir_exception)) 

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

554 

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

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

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

558 try: 

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

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

561 self.utilisation = int(st.st_size) 

562 except util.CommandException as inst: 

563 if inst.code == errno.EIO: 

564 raise xs_errors.XenError('VDILoad', \ 

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

566 else: 

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

568 self.path, str(inst))) 

569 raise xs_errors.XenError('VDIType', \ 

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

571 

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

573 self.exists = True 

574 self.size = self.utilisation 

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

576 return 

577 

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

579 self.exists = True 

580 self.size = self.utilisation 

581 return 

582 

583 try: 

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

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

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

587 

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

589 self.parent = image_info.parentUuid 

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

591 else: 

592 self.parent = "" 

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

594 self.size = image_info.sizeVirt 

595 self.hidden = image_info.hidden 

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

597 self.managed = False 

598 self.exists = True 

599 except util.CommandException as inst: 

600 raise xs_errors.XenError('VDILoad', \ 

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

602 

603 @override 

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

605 self.load(vdi_location) 

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

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

608 self._db_update() 

609 

610 @override 

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

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

613 raise xs_errors.XenError('VDIExists') 

614 

615 if VdiType.isCowImage(self.vdi_type): 

616 try: 

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

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

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

620 except util.CommandException as inst: 

621 raise xs_errors.XenError('VDICreate', 

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

623 else: 

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

625 f.truncate(int(size)) 

626 f.close() 

627 self.size = size 

628 

629 self.sr.added_vdi(self) 

630 

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

632 self.utilisation = int(st.st_size) 

633 if self.vdi_type == VdiType.RAW: 

634 # Legacy code. 

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

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

637 self.sm_config = {} 

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

639 

640 self._db_introduce() 

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

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

643 

644 @override 

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

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

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

648 

649 if self.attached: 

650 raise xs_errors.XenError('VDIInUse') 

651 

652 try: 

653 util.force_unlink(self.path) 

654 except Exception as e: 

655 raise xs_errors.XenError( 

656 'VDIDelete', 

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

658 

659 self.sr.deleted_vdi(vdi_uuid) 

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

661 if not data_only: 

662 self._db_forget() 

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

664 self.sr.lock.cleanupAll(vdi_uuid) 

665 self.sr._kickGC() 

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

667 

668 @override 

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

670 if self.path is None: 

671 self._find_path_with_retries(vdi_uuid) 

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

673 raise xs_errors.XenError('VDIUnavailable', \ 

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

675 try: 

676 self.attached = True 

677 

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

679 self.xenstore_data = {} 

680 

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

682 scsiutil.gen_synthetic_page_data(vdi_uuid))) 

683 

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

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

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

687 # pool database. 

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

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

690 except util.CommandException as inst: 

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

692 

693 @override 

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

695 self.attached = False 

696 

697 @override 

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

699 if not self.exists: 

700 raise xs_errors.XenError('VDIUnavailable', \ 

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

702 

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

704 raise xs_errors.XenError('Unimplemented') 

705 

706 if self.hidden: 

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

708 

709 if size < self.size: 

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

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

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

713 

714 if size == self.size: 

715 return VDI.VDI.get_params(self) 

716 

717 # We already checked it is a cow image. 

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

719 

720 jFile = JOURNAL_FILE_PREFIX + self.uuid 

721 try: 

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

723 except: 

724 # Revert the operation 

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

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

727 

728 old_size = self.size 

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

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

731 self.utilisation = int(st.st_size) 

732 

733 self._db_update() 

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

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

736 return VDI.VDI.get_params(self) 

737 

738 @override 

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

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

741 

742 @override 

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

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

745 raise xs_errors.XenError('Unimplemented') 

746 parent_fn = vdi1 + VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

748 assert(util.pathexists(parent_path)) 

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

750 self.cowutil.setHidden(parent_path) 

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

752 # Tell tapdisk the chain has changed 

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

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

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

756 

757 def reset_leaf(self, sr_uuid, vdi_uuid): 

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

759 raise xs_errors.XenError('Unimplemented') 

760 

761 # safety check 

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

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

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

765 

766 self.cowutil.killData(self.path) 

767 

768 @override 

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

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

771 # If cbt enabled, save file consistency state 

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

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

774 consistency_state = False 

775 else: 

776 consistency_state = True 

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

778 (consistency_state, vdi_uuid)) 

779 else: 

780 consistency_state = None 

781 

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

783 raise xs_errors.XenError('Unimplemented') 

784 

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

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

787 try: 

788 return self._snapshot(snapType, cbtlog, consistency_state) 

789 finally: 

790 self.disable_leaf_on_secondary(vdi_uuid, secondary=secondary) 

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

792 

793 @override 

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

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

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

797 

798 def _link(self, src, dst): 

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

800 os.link(src, dst) 

801 

802 def _unlink(self, path): 

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

804 os.unlink(path) 

805 

806 def _create_new_parent(self, src, newsrc): 

807 if self.sr._check_hardlinks(): 

808 self._link(src, newsrc) 

809 else: 

810 self._rename(src, newsrc) 

811 

812 def __fist_enospace(self): 

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

814 

815 def _snapshot(self, snap_type, cbtlog=None, cbt_consistency=None): 

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

817 

818 args = [] 

819 args.append("vdi_clone") 

820 args.append(self.sr.uuid) 

821 args.append(self.uuid) 

822 

823 dest = None 

824 dst = None 

825 extension = VDI_TYPE_TO_EXTENSION[self.vdi_type] 

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

827 dest = util.gen_uuid() 

828 dst = os.path.join(self.sr.path, "%s%s" % (dest, extension)) 

829 args.append(dest) 

830 

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

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

833 

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

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

836 raise xs_errors.XenError('VDIUnavailable', \ 

837 opterr='failed to get image depth') 

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

839 raise xs_errors.XenError('SnapshotChainTooLong') 

840 

841 newuuid = util.gen_uuid() 

842 src = self.path 

843 newsrc = os.path.join(self.sr.path, "%s%s" % (newuuid, extension)) 

844 newsrcname = "%s%s" % (newuuid, extension) 

845 

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

847 raise xs_errors.XenError('VDIUnavailable', \ 

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

849 

850 # wkcfix: multiphase 

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

852 

853 # We assume the filehandle has been released 

854 try: 

855 self._create_new_parent(src, newsrc) 

856 

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

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

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

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

861 # before so nobody will try to query it. 

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

863 # Fault injection site to fail the snapshot with ENOSPACE 

864 util.fistpoint.activate_custom_fn( 

865 "FileSR_fail_snap1", 

866 self.__fist_enospace) 

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

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

869 # hardlink leaf too quickly after creating it. 

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

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

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

873 # Fault injection site to fail the snapshot with ENOSPACE 

874 util.fistpoint.activate_custom_fn( 

875 "FileSR_fail_snap2", 

876 self.__fist_enospace) 

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

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

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

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

881 

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

883 introduce_parent = True 

884 try: 

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

886 dstparent = None 

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

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

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

890 (snap_type == VDI.SNAPSHOT_SINGLE or \ 

891 snap_type == VDI.SNAPSHOT_INTERNAL or \ 

892 dstparent != newuuid): 

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

894 introduce_parent = False 

895 except Exception as e: 

896 raise 

897 

898 # Introduce the new VDI records 

899 leaf_vdi = None 

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

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

902 leaf_vdi.read_only = False 

903 leaf_vdi.location = dest 

904 leaf_vdi.size = self.size 

905 leaf_vdi.utilisation = self.utilisation 

906 leaf_vdi.sm_config = {} 

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

908 # If the parent is encrypted set the key_hash 

909 # for the new snapshot disk 

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

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

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

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

914 # If we have CBT enabled on the VDI, 

915 # set CBT status for the new snapshot disk 

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

917 leaf_vdi.cbt_enabled = True 

918 

919 base_vdi = None 

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

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

922 base_vdi.label = "base copy" 

923 base_vdi.read_only = True 

924 base_vdi.location = newuuid 

925 base_vdi.size = self.size 

926 base_vdi.utilisation = self.utilisation 

927 base_vdi.sm_config = {} 

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

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

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

931 

932 try: 

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

934 leaf_vdi_ref = leaf_vdi._db_introduce() 

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

936 (leaf_vdi_ref, dest)) 

937 

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

939 base_vdi_ref = base_vdi._db_introduce() 

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

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

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

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

944 sm_config['vhd-parent'] = srcparent 

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

946 except Exception as e: 

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

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

949 # been marked as deleted already. 

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

951 raise 

952 except util.CommandException as inst: 

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

954 self._clonecleanup(src, dst, newsrc) 

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

956 raise xs_errors.XenError('VDIClone', 

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

958 

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

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

961 try: 

962 self._cbt_snapshot(dest, cbt_consistency) 

963 except: 

964 # CBT operation failed. 

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

966 raise 

967 

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

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

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

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

972 ret_vdi = leaf_vdi 

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

974 ret_vdi = base_vdi 

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

976 ret_vdi = self 

977 return ret_vdi.get_params() 

978 

979 @override 

980 def get_params(self) -> str: 

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

982 raise xs_errors.XenError('VDIUnavailable', \ 

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

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

985 

986 def _snap(self, child, parent): 

987 self.cowutil.snapshot(child, parent, self.vdi_type == VdiType.RAW) 

988 

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

990 try: 

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

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

993 except util.CommandException as inst: 

994 pass 

995 try: 

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

997 stats = os.stat(newsrc) 

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

999 if (stats.st_nlink > 1): 

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

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

1002 self._rename(newsrc, src) 

1003 except util.CommandException as inst: 

1004 pass 

1005 

1006 def _checkpath(self, path): 

1007 try: 

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

1009 return False 

1010 return True 

1011 except util.CommandException as inst: 

1012 raise xs_errors.XenError('EIO', \ 

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

1014 

1015 def _create(self, size, path): 

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

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

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

1019 

1020 def _mark_hidden(self, path): 

1021 self.cowutil.setHidden(path, True) 

1022 self.hidden = 1 

1023 

1024 def _is_hidden(self, path): 

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

1026 

1027 @staticmethod 

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

1029 fileName = os.path.basename(path) 

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

1031 

1032 @override 

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

1034 """ 

1035 Generate the XML config required to attach and activate 

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

1037 activation is handled by vdi_attach_from_config below. 

1038 """ 

1039 util.SMlog("FileVDI.generate_config") 

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

1041 raise xs_errors.XenError('VDIUnavailable') 

1042 resp = {} 

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

1044 resp['sr_uuid'] = sr_uuid 

1045 resp['vdi_uuid'] = vdi_uuid 

1046 resp['command'] = 'vdi_attach_from_config' 

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

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

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

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

1051 

1052 @override 

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

1054 """ 

1055 Attach and activate a VDI using config generated by 

1056 vdi_generate_config above. This is used for cases such as 

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

1058 """ 

1059 util.SMlog("FileVDI.attach_from_config") 

1060 try: 

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

1062 return self.sr.attach(sr_uuid) 

1063 except: 

1064 util.logException("FileVDI.attach_from_config") 

1065 raise xs_errors.XenError( 

1066 'SRUnavailable', 

1067 opterr='Unable to attach from config' 

1068 ) 

1069 return '' 

1070 

1071 @override 

1072 def _create_cbt_log(self) -> str: 

1073 # Create CBT log file 

1074 # Name: <vdi_uuid>.cbtlog 

1075 #Handle if file already exists 

1076 log_path = self._get_cbt_logpath(self.uuid) 

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

1078 open_file.close() 

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

1080 

1081 @override 

1082 def _delete_cbt_log(self) -> None: 

1083 logPath = self._get_cbt_logpath(self.uuid) 

1084 try: 

1085 os.remove(logPath) 

1086 except OSError as e: 

1087 if e.errno != errno.ENOENT: 

1088 raise 

1089 

1090 @override 

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

1092 return util.pathexists(logpath) 

1093 

1094 

1095class SharedFileSR(FileSR): 

1096 """ 

1097 FileSR subclass for SRs that use shared network storage 

1098 """ 

1099 

1100 def _check_writable(self): 

1101 """ 

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

1103 raising an exception if it can't. 

1104 """ 

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

1106 try: 

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

1108 except OSError as e: 

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

1110 raise xs_errors.XenError('SharedFileSystemNoWrite') 

1111 finally: 

1112 util.force_unlink(test_name) 

1113 

1114 def _raise_hardlink_error(self): 

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

1116 

1117 @override 

1118 def _check_hardlinks(self) -> bool: 

1119 hardlink_conf = self._read_hardlink_conf() 

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

1121 return hardlink_conf 

1122 

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

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

1125 

1126 link_name = '%s.new' % test_name 

1127 try: 

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

1129 util.fistpoint.activate_custom_fn( 

1130 "FileSR_fail_hardlink", 

1131 self._raise_hardlink_error) 

1132 

1133 os.link(test_name, link_name) 

1134 self._write_hardlink_conf(supported=True) 

1135 return True 

1136 except OSError: 

1137 self._write_hardlink_conf(supported=False) 

1138 

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

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

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

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

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

1144 try: 

1145 self.session.xenapi.message.create( 

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

1147 msg) 

1148 except XenAPI.Failure: 

1149 # Might already be set and checking has TOCTOU issues 

1150 pass 

1151 finally: 

1152 util.force_unlink(link_name) 

1153 util.force_unlink(test_name) 

1154 

1155 return False 

1156 

1157 def _get_hardlink_conf_path(self): 

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

1159 

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

1161 try: 

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

1163 try: 

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

1165 except Exception as e: 

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

1167 return None 

1168 except IOError as e: 

1169 if e.errno == errno.ENOENT: 

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

1171 return None 

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

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

1174 return None 

1175 

1176 def _write_hardlink_conf(self, supported): 

1177 try: 

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

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

1180 except Exception as e: 

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

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

1183 

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

1185 SRCommand.run(FileSR, DRIVER_INFO) 

1186else: 

1187 SR.registerSR(FileSR)