Coverage for libs/sm/cleanup.py: 39%

2441 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2026-07-10 07:59 +0000

1# 

2# Copyright (C) Citrix Systems Inc. 

3# 

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

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

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

7# 

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

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

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

11# GNU Lesser General Public License for more details. 

12# 

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

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

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

16# 

17import os 

18import os.path 

19import sys 

20import time 

21import signal 

22import subprocess 

23import datetime 

24import traceback 

25import base64 

26import zlib 

27import errno 

28import stat 

29 

30from sm.ipc import IPCFlag 

31from functools import reduce 

32from time import monotonic as _time 

33from sm import blktap2 

34import XenAPI # pylint: disable=import-error 

35 

36from sm import lvutil 

37from sm import vhdutil 

38from sm import lvhdutil 

39from sm import lvmcache 

40from sm import journaler 

41from sm import fjournaler 

42from sm.core import lock 

43from sm.core import util 

44from sm.refcounter import RefCounter 

45from sm.lvmanager import LVActivator 

46from sm.srmetadata import LVMMetadataHandler 

47 

48# Disable automatic leaf-coalescing. Online leaf-coalesce is currently not 

49# possible due to lvhd_stop_using_() not working correctly. However, we leave 

50# this option available through the explicit LEAFCLSC_FORCE flag in the VDI 

51# record for use by the offline tool (which makes the operation safe by pausing 

52# the VM first) 

53AUTO_ONLINE_LEAF_COALESCE_ENABLED = True 

54 

55FLAG_TYPE_ABORT = "abort" # flag to request aborting of GC/coalesce 

56 

57# process "lock", used simply as an indicator that a process already exists 

58# that is doing GC/coalesce on this SR (such a process holds the lock, and we 

59# check for the fact by trying the lock). 

60lockGCRunning = None 

61 

62# process "lock" to indicate that the GC process has been activated but may not 

63# yet be running, stops a second process from being started. 

64LOCK_TYPE_GC_ACTIVE = "gc_active" 

65lockGCActive = None 

66 

67# Default coalesce error rate limit, in messages per minute. A zero value 

68# disables throttling, and a negative value disables error reporting. 

69DEFAULT_COALESCE_ERR_RATE = 1.0 / 60 

70 

71COALESCE_LAST_ERR_TAG = 'last-coalesce-error' 

72COALESCE_ERR_RATE_TAG = 'coalesce-error-rate' 

73VAR_RUN = "/run/" 

74SPEED_LOG_ROOT = VAR_RUN + "{uuid}.speed_log" 

75 

76N_RUNNING_AVERAGE = 10 

77 

78NON_PERSISTENT_DIR = '/run/nonpersistent/sm' 

79 

80# Signal Handler 

81SIGTERM = False 

82 

83 

84class AbortException(util.SMException): 

85 pass 

86 

87 

88def receiveSignal(signalNumber, frame): 

89 global SIGTERM 

90 

91 util.SMlog("GC: recieved SIGTERM") 

92 SIGTERM = True 

93 return 

94 

95 

96################################################################################ 

97# 

98# Util 

99# 

100class Util: 

101 RET_RC = 1 

102 RET_STDOUT = 2 

103 RET_STDERR = 4 

104 

105 UUID_LEN = 36 

106 

107 PREFIX = {"G": 1024 * 1024 * 1024, "M": 1024 * 1024, "K": 1024} 

108 

109 def log(text): 

110 util.SMlog(text, ident="SMGC") 

111 log = staticmethod(log) 

112 

113 def logException(tag): 

114 info = sys.exc_info() 

115 if info[0] == SystemExit: 115 ↛ 117line 115 didn't jump to line 117, because the condition on line 115 was never true

116 # this should not be happening when catching "Exception", but it is 

117 sys.exit(0) 

118 tb = reduce(lambda a, b: "%s%s" % (a, b), traceback.format_tb(info[2])) 

119 Util.log("*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") 

120 Util.log(" ***********************") 

121 Util.log(" * E X C E P T I O N *") 

122 Util.log(" ***********************") 

123 Util.log("%s: EXCEPTION %s, %s" % (tag, info[0], info[1])) 

124 Util.log(tb) 

125 Util.log("*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*~*") 

126 logException = staticmethod(logException) 

127 

128 def doexec(args, expectedRC, inputtext=None, ret=None, log=True): 

129 "Execute a subprocess, then return its return code, stdout, stderr" 

130 proc = subprocess.Popen(args, 

131 stdin=subprocess.PIPE, \ 

132 stdout=subprocess.PIPE, \ 

133 stderr=subprocess.PIPE, \ 

134 shell=True, \ 

135 close_fds=True) 

136 (stdout, stderr) = proc.communicate(inputtext) 

137 stdout = str(stdout) 

138 stderr = str(stderr) 

139 rc = proc.returncode 

140 if log: 

141 Util.log("`%s`: %s" % (args, rc)) 

142 if type(expectedRC) != type([]): 

143 expectedRC = [expectedRC] 

144 if not rc in expectedRC: 

145 reason = stderr.strip() 

146 if stdout.strip(): 

147 reason = "%s (stdout: %s)" % (reason, stdout.strip()) 

148 Util.log("Failed: %s" % reason) 

149 raise util.CommandException(rc, args, reason) 

150 

151 if ret == Util.RET_RC: 

152 return rc 

153 if ret == Util.RET_STDERR: 

154 return stderr 

155 return stdout 

156 doexec = staticmethod(doexec) 

157 

158 def runAbortable(func, ret, ns, abortTest, pollInterval, timeOut): 

159 """execute func in a separate thread and kill it if abortTest signals 

160 so""" 

161 abortSignaled = abortTest() # check now before we clear resultFlag 

162 resultFlag = IPCFlag(ns) 

163 resultFlag.clearAll() 

164 pid = os.fork() 

165 if pid: 

166 startTime = _time() 

167 try: 

168 while True: 

169 if resultFlag.test("success"): 

170 Util.log(" Child process completed successfully") 

171 resultFlag.clear("success") 

172 return 

173 if resultFlag.test("failure"): 

174 resultFlag.clear("failure") 

175 raise util.SMException("Child process exited with error") 

176 if abortTest() or abortSignaled or SIGTERM: 

177 os.killpg(pid, signal.SIGKILL) 

178 raise AbortException("Aborting due to signal") 

179 if timeOut and _time() - startTime > timeOut: 

180 os.killpg(pid, signal.SIGKILL) 

181 resultFlag.clearAll() 

182 raise util.SMException("Timed out") 

183 time.sleep(pollInterval) 

184 finally: 

185 wait_pid = 0 

186 rc = -1 

187 count = 0 

188 while wait_pid == 0 and count < 10: 

189 wait_pid, rc = os.waitpid(pid, os.WNOHANG) 

190 if wait_pid == 0: 

191 time.sleep(2) 

192 count += 1 

193 

194 if wait_pid == 0: 

195 Util.log("runAbortable: wait for process completion timed out") 

196 else: 

197 os.setpgrp() 

198 try: 

199 if func() == ret: 

200 resultFlag.set("success") 

201 else: 

202 resultFlag.set("failure") 

203 except Exception as e: 

204 Util.log("Child process failed with : (%s)" % e) 

205 resultFlag.set("failure") 

206 Util.logException("This exception has occured") 

207 os._exit(0) 

208 runAbortable = staticmethod(runAbortable) 

209 

210 def num2str(number): 

211 for prefix in ("G", "M", "K"): 211 ↛ 214line 211 didn't jump to line 214, because the loop on line 211 didn't complete

212 if number >= Util.PREFIX[prefix]: 

213 return "%.3f%s" % (float(number) / Util.PREFIX[prefix], prefix) 

214 return "%s" % number 

215 num2str = staticmethod(num2str) 

216 

217 def numBits(val): 

218 count = 0 

219 while val: 

220 count += val & 1 

221 val = val >> 1 

222 return count 

223 numBits = staticmethod(numBits) 

224 

225 def countBits(bitmap1, bitmap2): 

226 """return bit count in the bitmap produced by ORing the two bitmaps""" 

227 len1 = len(bitmap1) 

228 len2 = len(bitmap2) 

229 lenLong = len1 

230 lenShort = len2 

231 bitmapLong = bitmap1 

232 if len2 > len1: 

233 lenLong = len2 

234 lenShort = len1 

235 bitmapLong = bitmap2 

236 

237 count = 0 

238 for i in range(lenShort): 

239 val = bitmap1[i] | bitmap2[i] 

240 count += Util.numBits(val) 

241 

242 for i in range(i + 1, lenLong): 

243 val = bitmapLong[i] 

244 count += Util.numBits(val) 

245 return count 

246 countBits = staticmethod(countBits) 

247 

248 

249################################################################################ 

250# 

251# XAPI 

252# 

253class XAPI: 

254 USER = "root" 

255 PLUGIN_ON_SLAVE = "on-slave" 

256 

257 CONFIG_SM = 0 

258 CONFIG_OTHER = 1 

259 CONFIG_ON_BOOT = 2 

260 CONFIG_ALLOW_CACHING = 3 

261 

262 CONFIG_NAME = { 

263 CONFIG_SM: "sm-config", 

264 CONFIG_OTHER: "other-config", 

265 CONFIG_ON_BOOT: "on-boot", 

266 CONFIG_ALLOW_CACHING: "allow_caching" 

267 } 

268 

269 class LookupError(util.SMException): 

270 pass 

271 

272 def getSession(): 

273 session = XenAPI.xapi_local() 

274 session.xenapi.login_with_password(XAPI.USER, '', '', 'SM') 

275 return session 

276 getSession = staticmethod(getSession) 

277 

278 def __init__(self, session, srUuid): 

279 self.sessionPrivate = False 

280 self.session = session 

281 if self.session is None: 

282 self.session = self.getSession() 

283 self.sessionPrivate = True 

284 self._srRef = self.session.xenapi.SR.get_by_uuid(srUuid) 

285 self.srRecord = self.session.xenapi.SR.get_record(self._srRef) 

286 self.hostUuid = util.get_this_host() 

287 self._hostRef = self.session.xenapi.host.get_by_uuid(self.hostUuid) 

288 self.task = None 

289 self.task_progress = {"coalescable": 0, "done": 0} 

290 

291 def __del__(self): 

292 if self.sessionPrivate: 

293 self.session.xenapi.session.logout() 

294 

295 @property 

296 def srRef(self): 

297 return self._srRef 

298 

299 def isPluggedHere(self): 

300 pbds = self.getAttachedPBDs() 

301 for pbdRec in pbds: 

302 if pbdRec["host"] == self._hostRef: 

303 return True 

304 return False 

305 

306 def poolOK(self): 

307 host_recs = self.session.xenapi.host.get_all_records() 

308 for host_ref, host_rec in host_recs.items(): 

309 if not host_rec["enabled"]: 

310 Util.log("Host %s not enabled" % host_rec["uuid"]) 

311 return False 

312 return True 

313 

314 def isMaster(self): 

315 if self.srRecord["shared"]: 

316 pool = list(self.session.xenapi.pool.get_all_records().values())[0] 

317 return pool["master"] == self._hostRef 

318 else: 

319 pbds = self.getAttachedPBDs() 

320 if len(pbds) < 1: 

321 raise util.SMException("Local SR not attached") 

322 elif len(pbds) > 1: 

323 raise util.SMException("Local SR multiply attached") 

324 return pbds[0]["host"] == self._hostRef 

325 

326 def getAttachedPBDs(self): 

327 """Return PBD records for all PBDs of this SR that are currently 

328 attached""" 

329 attachedPBDs = [] 

330 pbds = self.session.xenapi.PBD.get_all_records() 

331 for pbdRec in pbds.values(): 

332 if pbdRec["SR"] == self._srRef and pbdRec["currently_attached"]: 

333 attachedPBDs.append(pbdRec) 

334 return attachedPBDs 

335 

336 def getOnlineHosts(self): 

337 return util.get_online_hosts(self.session) 

338 

339 def ensureInactive(self, hostRef, args): 

340 text = self.session.xenapi.host.call_plugin( \ 

341 hostRef, self.PLUGIN_ON_SLAVE, "multi", args) 

342 Util.log("call-plugin returned: '%s'" % text) 

343 

344 def getRecordHost(self, hostRef): 

345 return self.session.xenapi.host.get_record(hostRef) 

346 

347 def _getRefVDI(self, uuid): 

348 return self.session.xenapi.VDI.get_by_uuid(uuid) 

349 

350 def getRefVDI(self, vdi): 

351 return self._getRefVDI(vdi.uuid) 

352 

353 def getRecordVDI(self, uuid): 

354 try: 

355 ref = self._getRefVDI(uuid) 

356 return self.session.xenapi.VDI.get_record(ref) 

357 except XenAPI.Failure: 

358 return None 

359 

360 def singleSnapshotVDI(self, vdi): 

361 return self.session.xenapi.VDI.snapshot(vdi.getRef(), 

362 {"type": "internal"}) 

363 

364 def forgetVDI(self, srUuid, vdiUuid): 

365 """Forget the VDI, but handle the case where the VDI has already been 

366 forgotten (i.e. ignore errors)""" 

367 try: 

368 vdiRef = self.session.xenapi.VDI.get_by_uuid(vdiUuid) 

369 self.session.xenapi.VDI.forget(vdiRef) 

370 except XenAPI.Failure: 

371 pass 

372 

373 def getConfigVDI(self, vdi, key): 

374 kind = vdi.CONFIG_TYPE[key] 

375 if kind == self.CONFIG_SM: 

376 cfg = self.session.xenapi.VDI.get_sm_config(vdi.getRef()) 

377 elif kind == self.CONFIG_OTHER: 

378 cfg = self.session.xenapi.VDI.get_other_config(vdi.getRef()) 

379 elif kind == self.CONFIG_ON_BOOT: 

380 cfg = self.session.xenapi.VDI.get_on_boot(vdi.getRef()) 

381 elif kind == self.CONFIG_ALLOW_CACHING: 

382 cfg = self.session.xenapi.VDI.get_allow_caching(vdi.getRef()) 

383 else: 

384 assert(False) 

385 Util.log("Got %s for %s: %s" % (self.CONFIG_NAME[kind], vdi, repr(cfg))) 

386 return cfg 

387 

388 def removeFromConfigVDI(self, vdi, key): 

389 kind = vdi.CONFIG_TYPE[key] 

390 if kind == self.CONFIG_SM: 

391 self.session.xenapi.VDI.remove_from_sm_config(vdi.getRef(), key) 

392 elif kind == self.CONFIG_OTHER: 

393 self.session.xenapi.VDI.remove_from_other_config(vdi.getRef(), key) 

394 else: 

395 assert(False) 

396 

397 def addToConfigVDI(self, vdi, key, val): 

398 kind = vdi.CONFIG_TYPE[key] 

399 if kind == self.CONFIG_SM: 

400 self.session.xenapi.VDI.add_to_sm_config(vdi.getRef(), key, val) 

401 elif kind == self.CONFIG_OTHER: 

402 self.session.xenapi.VDI.add_to_other_config(vdi.getRef(), key, val) 

403 else: 

404 assert(False) 

405 

406 def isSnapshot(self, vdi): 

407 return self.session.xenapi.VDI.get_is_a_snapshot(vdi.getRef()) 

408 

409 def markCacheSRsDirty(self): 

410 sr_refs = self.session.xenapi.SR.get_all_records_where( \ 

411 'field "local_cache_enabled" = "true"') 

412 for sr_ref in sr_refs: 

413 Util.log("Marking SR %s dirty" % sr_ref) 

414 util.set_dirty(self.session, sr_ref) 

415 

416 def srUpdate(self): 

417 Util.log("Starting asynch srUpdate for SR %s" % self.srRecord["uuid"]) 

418 abortFlag = IPCFlag(self.srRecord["uuid"]) 

419 task = self.session.xenapi.Async.SR.update(self._srRef) 

420 cancelTask = True 

421 try: 

422 for i in range(60): 

423 status = self.session.xenapi.task.get_status(task) 

424 if not status == "pending": 

425 Util.log("SR.update_asynch status changed to [%s]" % status) 

426 cancelTask = False 

427 return 

428 if abortFlag.test(FLAG_TYPE_ABORT): 

429 Util.log("Abort signalled during srUpdate, cancelling task...") 

430 try: 

431 self.session.xenapi.task.cancel(task) 

432 cancelTask = False 

433 Util.log("Task cancelled") 

434 except: 

435 pass 

436 return 

437 time.sleep(1) 

438 finally: 

439 if cancelTask: 

440 self.session.xenapi.task.cancel(task) 

441 self.session.xenapi.task.destroy(task) 

442 Util.log("Asynch srUpdate still running, but timeout exceeded.") 

443 

444 def update_task(self): 

445 self.session.xenapi.task.set_other_config( 

446 self.task, 

447 { 

448 "applies_to": self._srRef 

449 }) 

450 total = self.task_progress['coalescable'] + self.task_progress['done'] 

451 if (total > 0): 

452 self.session.xenapi.task.set_progress( 

453 self.task, float(self.task_progress['done']) / total) 

454 

455 def create_task(self, label, description): 

456 self.task = self.session.xenapi.task.create(label, description) 

457 self.update_task() 

458 

459 def update_task_progress(self, key, value): 

460 self.task_progress[key] = value 

461 if self.task: 

462 self.update_task() 

463 

464 def set_task_status(self, status): 

465 if self.task: 

466 self.session.xenapi.task.set_status(self.task, status) 

467 

468 

469################################################################################ 

470# 

471# VDI 

472# 

473class VDI: 

474 """Object representing a VDI of a VHD-based SR""" 

475 

476 POLL_INTERVAL = 1 

477 POLL_TIMEOUT = 30 

478 DEVICE_MAJOR = 202 

479 DRIVER_NAME_VHD = "vhd" 

480 

481 # config keys & values 

482 DB_VHD_PARENT = "vhd-parent" 

483 DB_VDI_TYPE = "vdi_type" 

484 DB_VHD_BLOCKS = "vhd-blocks" 

485 DB_VDI_PAUSED = "paused" 

486 DB_VDI_RELINKING = "relinking" 

487 DB_VDI_ACTIVATING = "activating" 

488 DB_GC = "gc" 

489 DB_COALESCE = "coalesce" 

490 DB_LEAFCLSC = "leaf-coalesce" # config key 

491 DB_GC_NO_SPACE = "gc_no_space" 

492 LEAFCLSC_DISABLED = "false" # set by user; means do not leaf-coalesce 

493 LEAFCLSC_FORCE = "force" # set by user; means skip snap-coalesce 

494 LEAFCLSC_OFFLINE = "offline" # set here for informational purposes: means 

495 # no space to snap-coalesce or unable to keep 

496 # up with VDI. This is not used by the SM, it 

497 # might be used by external components. 

498 DB_ONBOOT = "on-boot" 

499 ONBOOT_RESET = "reset" 

500 DB_ALLOW_CACHING = "allow_caching" 

501 

502 CONFIG_TYPE = { 

503 DB_VHD_PARENT: XAPI.CONFIG_SM, 

504 DB_VDI_TYPE: XAPI.CONFIG_SM, 

505 DB_VHD_BLOCKS: XAPI.CONFIG_SM, 

506 DB_VDI_PAUSED: XAPI.CONFIG_SM, 

507 DB_VDI_RELINKING: XAPI.CONFIG_SM, 

508 DB_VDI_ACTIVATING: XAPI.CONFIG_SM, 

509 DB_GC: XAPI.CONFIG_OTHER, 

510 DB_COALESCE: XAPI.CONFIG_OTHER, 

511 DB_LEAFCLSC: XAPI.CONFIG_OTHER, 

512 DB_ONBOOT: XAPI.CONFIG_ON_BOOT, 

513 DB_ALLOW_CACHING: XAPI.CONFIG_ALLOW_CACHING, 

514 DB_GC_NO_SPACE: XAPI.CONFIG_SM 

515 } 

516 

517 LIVE_LEAF_COALESCE_MAX_SIZE = 20 * 1024 * 1024 # bytes 

518 LIVE_LEAF_COALESCE_TIMEOUT = 10 # seconds 

519 TIMEOUT_SAFETY_MARGIN = 0.5 # extra margin when calculating 

520 # feasibility of leaf coalesce 

521 

522 JRN_RELINK = "relink" # journal entry type for relinking children 

523 JRN_COALESCE = "coalesce" # to communicate which VDI is being coalesced 

524 JRN_LEAF = "leaf" # used in coalesce-leaf 

525 

526 STR_TREE_INDENT = 4 

527 

528 def __init__(self, sr, uuid, raw): 

529 self.sr = sr 

530 self.scanError = True 

531 self.uuid = uuid 

532 self.raw = raw 

533 self.fileName = "" 

534 self.parentUuid = "" 

535 self.sizeVirt = -1 

536 self._sizeVHD = -1 

537 self._sizeAllocated = -1 

538 self._hidden = False 

539 self.parent = None 

540 self.children = [] 

541 self._vdiRef = None 

542 self._clearRef() 

543 

544 @staticmethod 

545 def extractUuid(path): 

546 raise NotImplementedError("Implement in sub class") 

547 

548 def load(self, info=None): 

549 """Load VDI info""" 

550 pass # abstract 

551 

552 def getDriverName(self): 

553 return self.DRIVER_NAME_VHD 

554 

555 def getRef(self): 

556 if self._vdiRef is None: 

557 self._vdiRef = self.sr.xapi.getRefVDI(self) 

558 return self._vdiRef 

559 

560 def getConfig(self, key, default=None): 

561 config = self.sr.xapi.getConfigVDI(self, key) 

562 if key == self.DB_ONBOOT or key == self.DB_ALLOW_CACHING: 562 ↛ 563line 562 didn't jump to line 563, because the condition on line 562 was never true

563 val = config 

564 else: 

565 val = config.get(key) 

566 if val: 

567 return val 

568 return default 

569 

570 def setConfig(self, key, val): 

571 self.sr.xapi.removeFromConfigVDI(self, key) 

572 self.sr.xapi.addToConfigVDI(self, key, val) 

573 Util.log("Set %s = %s for %s" % (key, val, self)) 

574 

575 def delConfig(self, key): 

576 self.sr.xapi.removeFromConfigVDI(self, key) 

577 Util.log("Removed %s from %s" % (key, self)) 

578 

579 def ensureUnpaused(self): 

580 if self.getConfig(self.DB_VDI_PAUSED) == "true": 

581 Util.log("Unpausing VDI %s" % self) 

582 self.unpause() 

583 

584 def pause(self, failfast=False): 

585 if not blktap2.VDI.tap_pause(self.sr.xapi.session, self.sr.uuid, 

586 self.uuid, failfast): 

587 raise util.SMException("Failed to pause VDI %s" % self) 

588 

589 def _report_tapdisk_unpause_error(self): 

590 try: 

591 xapi = self.sr.xapi.session.xenapi 

592 sr_ref = xapi.SR.get_by_uuid(self.sr.uuid) 

593 msg_name = "failed to unpause tapdisk" 

594 msg_body = "Failed to unpause tapdisk for VDI %s, " \ 

595 "VMs using this tapdisk have lost access " \ 

596 "to the corresponding disk(s)" % self.uuid 

597 xapi.message.create(msg_name, "4", "SR", self.sr.uuid, msg_body) 

598 except Exception as e: 

599 util.SMlog("failed to generate message: %s" % e) 

600 

601 def unpause(self): 

602 if not blktap2.VDI.tap_unpause(self.sr.xapi.session, self.sr.uuid, 

603 self.uuid): 

604 self._report_tapdisk_unpause_error() 

605 raise util.SMException("Failed to unpause VDI %s" % self) 

606 

607 def refresh(self, ignoreNonexistent=True): 

608 """Pause-unpause in one step""" 

609 self.sr.lock() 

610 try: 

611 try: 

612 if not blktap2.VDI.tap_refresh(self.sr.xapi.session, 612 ↛ 614line 612 didn't jump to line 614, because the condition on line 612 was never true

613 self.sr.uuid, self.uuid): 

614 self._report_tapdisk_unpause_error() 

615 raise util.SMException("Failed to refresh %s" % self) 

616 except XenAPI.Failure as e: 

617 if util.isInvalidVDI(e) and ignoreNonexistent: 

618 Util.log("VDI %s not found, ignoring" % self) 

619 return 

620 raise 

621 finally: 

622 self.sr.unlock() 

623 

624 def isSnapshot(self): 

625 return self.sr.xapi.isSnapshot(self) 

626 

627 def isAttachedRW(self): 

628 return util.is_attached_rw( 

629 self.sr.xapi.session.xenapi.VDI.get_sm_config(self.getRef())) 

630 

631 def getVHDBlocks(self): 

632 val = self.updateBlockInfo() 

633 bitmap = zlib.decompress(base64.b64decode(val)) 

634 return bitmap 

635 

636 def isCoalesceable(self): 

637 """A VDI is coalesceable if it has no siblings and is not a leaf""" 

638 return not self.scanError and \ 

639 self.parent and \ 

640 len(self.parent.children) == 1 and \ 

641 self.isHidden() and \ 

642 len(self.children) > 0 

643 

644 def isLeafCoalesceable(self): 

645 """A VDI is leaf-coalesceable if it has no siblings and is a leaf""" 

646 return not self.scanError and \ 

647 self.parent and \ 

648 len(self.parent.children) == 1 and \ 

649 not self.isHidden() and \ 

650 len(self.children) == 0 

651 

652 def canLiveCoalesce(self, speed): 

653 """Can we stop-and-leaf-coalesce this VDI? The VDI must be 

654 isLeafCoalesceable() already""" 

655 feasibleSize = False 

656 allowedDownTime = \ 

657 self.TIMEOUT_SAFETY_MARGIN * self.LIVE_LEAF_COALESCE_TIMEOUT 

658 vhd_size = self.getAllocatedSize() 

659 if speed: 

660 feasibleSize = \ 

661 vhd_size // speed < allowedDownTime 

662 else: 

663 feasibleSize = \ 

664 vhd_size < self.LIVE_LEAF_COALESCE_MAX_SIZE 

665 

666 return (feasibleSize or 

667 self.getConfig(self.DB_LEAFCLSC) == self.LEAFCLSC_FORCE) 

668 

669 def getAllPrunable(self): 

670 if len(self.children) == 0: # base case 

671 # it is possible to have a hidden leaf that was recently coalesced 

672 # onto its parent, its children already relinked but not yet 

673 # reloaded - in which case it may not be garbage collected yet: 

674 # some tapdisks could still be using the file. 

675 if self.sr.journaler.get(self.JRN_RELINK, self.uuid): 

676 return [] 

677 if not self.scanError and self.isHidden(): 

678 return [self] 

679 return [] 

680 

681 thisPrunable = True 

682 vdiList = [] 

683 for child in self.children: 

684 childList = child.getAllPrunable() 

685 vdiList.extend(childList) 

686 if child not in childList: 

687 thisPrunable = False 

688 

689 if not self.scanError and thisPrunable: 

690 vdiList.append(self) 

691 return vdiList 

692 

693 def getSizeVHD(self): 

694 return self._sizeVHD 

695 

696 def getAllocatedSize(self): 

697 return self._sizeAllocated 

698 

699 def getTreeRoot(self): 

700 "Get the root of the tree that self belongs to" 

701 root = self 

702 while root.parent: 

703 root = root.parent 

704 return root 

705 

706 def getTreeHeight(self): 

707 "Get the height of the subtree rooted at self" 

708 if len(self.children) == 0: 

709 return 1 

710 

711 maxChildHeight = 0 

712 for child in self.children: 

713 childHeight = child.getTreeHeight() 

714 if childHeight > maxChildHeight: 

715 maxChildHeight = childHeight 

716 

717 return maxChildHeight + 1 

718 

719 def getAllLeaves(self): 

720 "Get all leaf nodes in the subtree rooted at self" 

721 if len(self.children) == 0: 

722 return [self] 

723 

724 leaves = [] 

725 for child in self.children: 

726 leaves.extend(child.getAllLeaves()) 

727 return leaves 

728 

729 def updateBlockInfo(self): 

730 val = base64.b64encode(self._queryVHDBlocks()).decode() 

731 self.setConfig(VDI.DB_VHD_BLOCKS, val) 

732 return val 

733 

734 def rename(self, uuid): 

735 "Rename the VDI file" 

736 assert(not self.sr.vdis.get(uuid)) 

737 self._clearRef() 

738 oldUuid = self.uuid 

739 self.uuid = uuid 

740 self.children = [] 

741 # updating the children themselves is the responsibility of the caller 

742 del self.sr.vdis[oldUuid] 

743 self.sr.vdis[self.uuid] = self 

744 

745 def delete(self): 

746 "Physically delete the VDI" 

747 lock.Lock.cleanup(self.uuid, lvhdutil.NS_PREFIX_LVM + self.sr.uuid) 

748 lock.Lock.cleanupAll(self.uuid) 

749 self._clear() 

750 

751 def __str__(self): 

752 strHidden = "" 

753 if self.isHidden(): 753 ↛ 754line 753 didn't jump to line 754, because the condition on line 753 was never true

754 strHidden = "*" 

755 strSizeVirt = "?" 

756 if self.sizeVirt > 0: 756 ↛ 757line 756 didn't jump to line 757, because the condition on line 756 was never true

757 strSizeVirt = Util.num2str(self.sizeVirt) 

758 strSizeVHD = "?" 

759 if self._sizeVHD > 0: 759 ↛ 760line 759 didn't jump to line 760, because the condition on line 759 was never true

760 strSizeVHD = "/%s" % Util.num2str(self._sizeVHD) 

761 strSizeAllocated = "?" 

762 if self._sizeAllocated >= 0: 

763 strSizeAllocated = "/%s" % Util.num2str(self._sizeAllocated) 

764 strType = "" 

765 if self.raw: 

766 strType = "[RAW]" 

767 strSizeVHD = "" 

768 

769 return "%s%s(%s%s%s)%s" % (strHidden, self.uuid[0:8], strSizeVirt, 

770 strSizeVHD, strSizeAllocated, strType) 

771 

772 def validate(self, fast=False): 

773 if not vhdutil.check(self.path, fast=fast): 773 ↛ 774line 773 didn't jump to line 774, because the condition on line 773 was never true

774 raise util.SMException("VHD %s corrupted" % self) 

775 

776 def _clear(self): 

777 self.uuid = "" 

778 self.path = "" 

779 self.parentUuid = "" 

780 self.parent = None 

781 self._clearRef() 

782 

783 def _clearRef(self): 

784 self._vdiRef = None 

785 

786 def _doCoalesce(self): 

787 """Coalesce self onto parent. Only perform the actual coalescing of 

788 VHD, but not the subsequent relinking. We'll do that as the next step, 

789 after reloading the entire SR in case things have changed while we 

790 were coalescing""" 

791 self.validate() 

792 self.parent.validate(True) 

793 self.parent._increaseSizeVirt(self.sizeVirt) 

794 self.sr._updateSlavesOnResize(self.parent) 

795 self._coalesceVHD(0) 

796 self.parent.validate(True) 

797 #self._verifyContents(0) 

798 self.parent.updateBlockInfo() 

799 

800 def _verifyContents(self, timeOut): 

801 Util.log(" Coalesce verification on %s" % self) 

802 abortTest = lambda: IPCFlag(self.sr.uuid).test(FLAG_TYPE_ABORT) 

803 Util.runAbortable(lambda: self._runTapdiskDiff(), True, 

804 self.sr.uuid, abortTest, VDI.POLL_INTERVAL, timeOut) 

805 Util.log(" Coalesce verification succeeded") 

806 

807 def _runTapdiskDiff(self): 

808 cmd = "tapdisk-diff -n %s:%s -m %s:%s" % \ 

809 (self.getDriverName(), self.path, \ 

810 self.parent.getDriverName(), self.parent.path) 

811 Util.doexec(cmd, 0) 

812 return True 

813 

814 def _reportCoalesceError(vdi, ce): 

815 """Reports a coalesce error to XenCenter. 

816 

817 vdi: the VDI object on which the coalesce error occured 

818 ce: the CommandException that was raised""" 

819 

820 msg_name = os.strerror(ce.code) 

821 if ce.code == errno.ENOSPC: 

822 # TODO We could add more information here, e.g. exactly how much 

823 # space is required for the particular coalesce, as well as actions 

824 # to be taken by the user and consequences of not taking these 

825 # actions. 

826 msg_body = 'Run out of space while coalescing.' 

827 elif ce.code == errno.EIO: 

828 msg_body = 'I/O error while coalescing.' 

829 else: 

830 msg_body = '' 

831 util.SMlog('Coalesce failed on SR %s: %s (%s)' 

832 % (vdi.sr.uuid, msg_name, msg_body)) 

833 

834 # Create a XenCenter message, but don't spam. 

835 xapi = vdi.sr.xapi.session.xenapi 

836 sr_ref = xapi.SR.get_by_uuid(vdi.sr.uuid) 

837 oth_cfg = xapi.SR.get_other_config(sr_ref) 

838 if COALESCE_ERR_RATE_TAG in oth_cfg: 

839 coalesce_err_rate = float(oth_cfg[COALESCE_ERR_RATE_TAG]) 

840 else: 

841 coalesce_err_rate = DEFAULT_COALESCE_ERR_RATE 

842 

843 xcmsg = False 

844 if coalesce_err_rate == 0: 

845 xcmsg = True 

846 elif coalesce_err_rate > 0: 

847 now = datetime.datetime.now() 

848 sm_cfg = xapi.SR.get_sm_config(sr_ref) 

849 if COALESCE_LAST_ERR_TAG in sm_cfg: 

850 # seconds per message (minimum distance in time between two 

851 # messages in seconds) 

852 spm = datetime.timedelta(seconds=(1.0 / coalesce_err_rate) * 60) 

853 last = datetime.datetime.fromtimestamp( 

854 float(sm_cfg[COALESCE_LAST_ERR_TAG])) 

855 if now - last >= spm: 

856 xapi.SR.remove_from_sm_config(sr_ref, 

857 COALESCE_LAST_ERR_TAG) 

858 xcmsg = True 

859 else: 

860 xcmsg = True 

861 if xcmsg: 

862 xapi.SR.add_to_sm_config(sr_ref, COALESCE_LAST_ERR_TAG, 

863 str(now.strftime('%s'))) 

864 if xcmsg: 

865 xapi.message.create(msg_name, "3", "SR", vdi.sr.uuid, msg_body) 

866 _reportCoalesceError = staticmethod(_reportCoalesceError) 

867 

868 def _doCoalesceVHD(vdi): 

869 try: 

870 startTime = time.time() 

871 vhdSize = vdi.getAllocatedSize() 

872 # size is returned in sectors 

873 coalesced_size = vhdutil.coalesce(vdi.path) * 512 

874 endTime = time.time() 

875 vdi.sr.recordStorageSpeed(startTime, endTime, coalesced_size) 

876 except util.CommandException as ce: 

877 # We use try/except for the following piece of code because it runs 

878 # in a separate process context and errors will not be caught and 

879 # reported by anyone. 

880 try: 

881 # Report coalesce errors back to user via XC 

882 VDI._reportCoalesceError(vdi, ce) 

883 except Exception as e: 

884 util.SMlog('failed to create XenCenter message: %s' % e) 

885 raise ce 

886 except: 

887 raise 

888 _doCoalesceVHD = staticmethod(_doCoalesceVHD) 

889 

890 def _vdi_is_raw(self, vdi_path): 

891 """ 

892 Given path to vdi determine if it is raw 

893 """ 

894 uuid = self.extractUuid(vdi_path) 

895 return self.sr.vdis[uuid].raw 

896 

897 def _coalesceVHD(self, timeOut): 

898 Util.log(" Running VHD coalesce on %s" % self) 

899 abortTest = lambda: IPCFlag(self.sr.uuid).test(FLAG_TYPE_ABORT) 899 ↛ exitline 899 didn't run the lambda on line 899

900 try: 

901 util.fistpoint.activate_custom_fn( 

902 "cleanup_coalesceVHD_inject_failure", 

903 util.inject_failure) 

904 Util.runAbortable(lambda: VDI._doCoalesceVHD(self), None, 

905 self.sr.uuid, abortTest, VDI.POLL_INTERVAL, timeOut) 

906 except: 

907 #exception at this phase could indicate a failure in vhd coalesce 

908 # or a kill of vhd coalesce by runAbortable due to timeOut 

909 # Try a repair and reraise the exception 

910 parent = "" 

911 try: 

912 parent = vhdutil.getParent(self.path, lambda x: x.strip()) 912 ↛ exitline 912 didn't run the lambda on line 912

913 if not self._vdi_is_raw(parent): 

914 # Repair error is logged and ignored. Error reraised later 

915 util.SMlog('Coalesce failed on %s, attempting repair on ' \ 

916 'parent %s' % (self.uuid, parent)) 

917 vhdutil.repair(parent) 

918 except Exception as e: 

919 util.SMlog('(error ignored) Failed to repair parent %s ' \ 

920 'after failed coalesce on %s, err: %s' % 

921 (parent, self.path, e)) 

922 raise 

923 

924 util.fistpoint.activate("LVHDRT_coalescing_VHD_data", self.sr.uuid) 

925 

926 def _relinkSkip(self): 

927 """Relink children of this VDI to point to the parent of this VDI""" 

928 abortFlag = IPCFlag(self.sr.uuid) 

929 for child in self.children: 

930 if abortFlag.test(FLAG_TYPE_ABORT): 930 ↛ 931line 930 didn't jump to line 931, because the condition on line 930 was never true

931 raise AbortException("Aborting due to signal") 

932 Util.log(" Relinking %s from %s to %s" % \ 

933 (child, self, self.parent)) 

934 util.fistpoint.activate("LVHDRT_relinking_grandchildren", self.sr.uuid) 

935 child._setParent(self.parent) 

936 self.children = [] 

937 

938 def _reloadChildren(self, vdiSkip): 

939 """Pause & unpause all VDIs in the subtree to cause blktap to reload 

940 the VHD metadata for this file in any online VDI""" 

941 abortFlag = IPCFlag(self.sr.uuid) 

942 for child in self.children: 

943 if child == vdiSkip: 

944 continue 

945 if abortFlag.test(FLAG_TYPE_ABORT): 945 ↛ 946line 945 didn't jump to line 946, because the condition on line 945 was never true

946 raise AbortException("Aborting due to signal") 

947 Util.log(" Reloading VDI %s" % child) 

948 child._reload() 

949 

950 def _reload(self): 

951 """Pause & unpause to cause blktap to reload the VHD metadata""" 

952 for child in self.children: 952 ↛ 953line 952 didn't jump to line 953, because the loop on line 952 never started

953 child._reload() 

954 

955 # only leaves can be attached 

956 if len(self.children) == 0: 956 ↛ exitline 956 didn't return from function '_reload', because the condition on line 956 was never false

957 try: 

958 self.delConfig(VDI.DB_VDI_RELINKING) 

959 except XenAPI.Failure as e: 

960 if not util.isInvalidVDI(e): 

961 raise 

962 self.refresh() 

963 

964 def _tagChildrenForRelink(self): 

965 if len(self.children) == 0: 

966 retries = 0 

967 try: 

968 while retries < 15: 

969 retries += 1 

970 if self.getConfig(VDI.DB_VDI_ACTIVATING) is not None: 

971 Util.log("VDI %s is activating, wait to relink" % 

972 self.uuid) 

973 else: 

974 self.setConfig(VDI.DB_VDI_RELINKING, "True") 

975 

976 if self.getConfig(VDI.DB_VDI_ACTIVATING): 

977 self.delConfig(VDI.DB_VDI_RELINKING) 

978 Util.log("VDI %s started activating while tagging" % 

979 self.uuid) 

980 else: 

981 return 

982 time.sleep(2) 

983 

984 raise util.SMException("Failed to tag vdi %s for relink" % self) 

985 except XenAPI.Failure as e: 

986 if not util.isInvalidVDI(e): 

987 raise 

988 

989 for child in self.children: 

990 child._tagChildrenForRelink() 

991 

992 def _loadInfoParent(self): 

993 ret = vhdutil.getParent(self.path, lvhdutil.extractUuid) 

994 if ret: 

995 self.parentUuid = ret 

996 

997 def _setParent(self, parent): 

998 vhdutil.setParent(self.path, parent.path, False) 

999 self.parent = parent 

1000 self.parentUuid = parent.uuid 

1001 parent.children.append(self) 

1002 try: 

1003 self.setConfig(self.DB_VHD_PARENT, self.parentUuid) 

1004 Util.log("Updated the vhd-parent field for child %s with %s" % \ 

1005 (self.uuid, self.parentUuid)) 

1006 except: 

1007 Util.log("Failed to update %s with vhd-parent field %s" % \ 

1008 (self.uuid, self.parentUuid)) 

1009 

1010 def _ensureParentActiveForRelink(self): 

1011 pass 

1012 

1013 def isHidden(self): 

1014 if self._hidden is None: 1014 ↛ 1015line 1014 didn't jump to line 1015, because the condition on line 1014 was never true

1015 self._loadInfoHidden() 

1016 return self._hidden 

1017 

1018 def _loadInfoHidden(self): 

1019 hidden = vhdutil.getHidden(self.path) 

1020 self._hidden = (hidden != 0) 

1021 

1022 def _setHidden(self, hidden=True): 

1023 self._hidden = None 

1024 vhdutil.setHidden(self.path, hidden) 

1025 self._hidden = hidden 

1026 

1027 def _increaseSizeVirt(self, size, atomic=True): 

1028 """ensure the virtual size of 'self' is at least 'size'. Note that 

1029 resizing a VHD must always be offline and atomically: the file must 

1030 not be open by anyone and no concurrent operations may take place. 

1031 Thus we use the Agent API call for performing paused atomic 

1032 operations. If the caller is already in the atomic context, it must 

1033 call with atomic = False""" 

1034 if self.sizeVirt >= size: 1034 ↛ 1036line 1034 didn't jump to line 1036, because the condition on line 1034 was never false

1035 return 

1036 Util.log(" Expanding VHD virt size for VDI %s: %s -> %s" % \ 

1037 (self, Util.num2str(self.sizeVirt), Util.num2str(size))) 

1038 

1039 msize = vhdutil.getMaxResizeSize(self.path) * 1024 * 1024 

1040 if (size <= msize): 

1041 vhdutil.setSizeVirtFast(self.path, size) 

1042 else: 

1043 if atomic: 

1044 vdiList = self._getAllSubtree() 

1045 self.sr.lock() 

1046 try: 

1047 self.sr.pauseVDIs(vdiList) 

1048 try: 

1049 self._setSizeVirt(size) 

1050 finally: 

1051 self.sr.unpauseVDIs(vdiList) 

1052 finally: 

1053 self.sr.unlock() 

1054 else: 

1055 self._setSizeVirt(size) 

1056 

1057 self.sizeVirt = vhdutil.getSizeVirt(self.path) 

1058 

1059 def _setSizeVirt(self, size): 

1060 """WARNING: do not call this method directly unless all VDIs in the 

1061 subtree are guaranteed to be unplugged (and remain so for the duration 

1062 of the operation): this operation is only safe for offline VHDs""" 

1063 jFile = os.path.join(self.sr.path, self.uuid) 

1064 vhdutil.setSizeVirt(self.path, size, jFile) 

1065 

1066 def _queryVHDBlocks(self): 

1067 return vhdutil.getBlockBitmap(self.path) 

1068 

1069 def _getCoalescedSizeData(self): 

1070 """Get the data size of the resulting VHD if we coalesce self onto 

1071 parent. We calculate the actual size by using the VHD block allocation 

1072 information (as opposed to just adding up the two VHD sizes to get an 

1073 upper bound)""" 

1074 # make sure we don't use stale BAT info from vdi_rec since the child 

1075 # was writable all this time 

1076 self.delConfig(VDI.DB_VHD_BLOCKS) 

1077 blocksChild = self.getVHDBlocks() 

1078 blocksParent = self.parent.getVHDBlocks() 

1079 numBlocks = Util.countBits(blocksChild, blocksParent) 

1080 Util.log("Num combined blocks = %d" % numBlocks) 

1081 sizeData = numBlocks * vhdutil.VHD_BLOCK_SIZE 

1082 assert(sizeData <= self.sizeVirt) 

1083 return sizeData 

1084 

1085 def _calcExtraSpaceForCoalescing(self): 

1086 sizeData = self._getCoalescedSizeData() 

1087 sizeCoalesced = sizeData + vhdutil.calcOverheadBitmap(sizeData) + \ 

1088 vhdutil.calcOverheadEmpty(self.sizeVirt) 

1089 Util.log("Coalesced size = %s" % Util.num2str(sizeCoalesced)) 

1090 return sizeCoalesced - self.parent.getSizeVHD() 

1091 

1092 def _calcExtraSpaceForLeafCoalescing(self): 

1093 """How much extra space in the SR will be required to 

1094 [live-]leaf-coalesce this VDI""" 

1095 # the space requirements are the same as for inline coalesce 

1096 return self._calcExtraSpaceForCoalescing() 

1097 

1098 def _calcExtraSpaceForSnapshotCoalescing(self): 

1099 """How much extra space in the SR will be required to 

1100 snapshot-coalesce this VDI""" 

1101 return self._calcExtraSpaceForCoalescing() + \ 

1102 vhdutil.calcOverheadEmpty(self.sizeVirt) # extra snap leaf 

1103 

1104 def _getAllSubtree(self): 

1105 """Get self and all VDIs in the subtree of self as a flat list""" 

1106 vdiList = [self] 

1107 for child in self.children: 

1108 vdiList.extend(child._getAllSubtree()) 

1109 return vdiList 

1110 

1111 

1112class FileVDI(VDI): 

1113 """Object representing a VDI in a file-based SR (EXT or NFS)""" 

1114 

1115 @staticmethod 

1116 def extractUuid(path): 

1117 path = os.path.basename(path.strip()) 

1118 if not (path.endswith(vhdutil.FILE_EXTN_VHD) or \ 1118 ↛ 1120line 1118 didn't jump to line 1120, because the condition on line 1118 was never true

1119 path.endswith(vhdutil.FILE_EXTN_RAW)): 

1120 return None 

1121 uuid = path.replace(vhdutil.FILE_EXTN_VHD, "").replace( \ 

1122 vhdutil.FILE_EXTN_RAW, "") 

1123 # TODO: validate UUID format 

1124 return uuid 

1125 

1126 def __init__(self, sr, uuid, raw): 

1127 VDI.__init__(self, sr, uuid, raw) 

1128 if self.raw: 1128 ↛ 1129line 1128 didn't jump to line 1129, because the condition on line 1128 was never true

1129 self.fileName = "%s%s" % (self.uuid, vhdutil.FILE_EXTN_RAW) 

1130 else: 

1131 self.fileName = "%s%s" % (self.uuid, vhdutil.FILE_EXTN_VHD) 

1132 

1133 def load(self, info=None): 

1134 if not info: 

1135 if not util.pathexists(self.path): 

1136 raise util.SMException("%s not found" % self.path) 

1137 try: 

1138 info = vhdutil.getVHDInfo(self.path, self.extractUuid) 

1139 except util.SMException: 

1140 Util.log(" [VDI %s: failed to read VHD metadata]" % self.uuid) 

1141 return 

1142 self.parent = None 

1143 self.children = [] 

1144 self.parentUuid = info.parentUuid 

1145 self.sizeVirt = info.sizeVirt 

1146 self._sizeVHD = info.sizePhys 

1147 self._sizeAllocated = info.sizeAllocated 

1148 self._hidden = info.hidden 

1149 self.scanError = False 

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

1151 (self.uuid, vhdutil.FILE_EXTN_VHD)) 

1152 

1153 def rename(self, uuid): 

1154 oldPath = self.path 

1155 VDI.rename(self, uuid) 

1156 self.fileName = "%s%s" % (self.uuid, vhdutil.FILE_EXTN_VHD) 

1157 self.path = os.path.join(self.sr.path, self.fileName) 

1158 assert(not util.pathexists(self.path)) 

1159 Util.log("Renaming %s -> %s" % (oldPath, self.path)) 

1160 os.rename(oldPath, self.path) 

1161 

1162 def delete(self): 

1163 if len(self.children) > 0: 1163 ↛ 1164line 1163 didn't jump to line 1164, because the condition on line 1163 was never true

1164 raise util.SMException("VDI %s has children, can't delete" % \ 

1165 self.uuid) 

1166 try: 

1167 self.sr.lock() 

1168 try: 

1169 os.unlink(self.path) 

1170 self.sr.forgetVDI(self.uuid) 

1171 finally: 

1172 self.sr.unlock() 

1173 except OSError: 

1174 raise util.SMException("os.unlink(%s) failed" % self.path) 

1175 VDI.delete(self) 

1176 

1177 def getAllocatedSize(self): 

1178 if self._sizeAllocated == -1: 1178 ↛ 1179line 1178 didn't jump to line 1179, because the condition on line 1178 was never true

1179 self._sizeAllocated = vhdutil.getAllocatedSize(self.path) 

1180 return self._sizeAllocated 

1181 

1182 

1183class LVHDVDI(VDI): 

1184 """Object representing a VDI in an LVHD SR""" 

1185 

1186 JRN_ZERO = "zero" # journal entry type for zeroing out end of parent 

1187 DRIVER_NAME_RAW = "aio" 

1188 

1189 def load(self, vdiInfo): 

1190 self.parent = None 

1191 self.children = [] 

1192 self._sizeVHD = -1 

1193 self._sizeAllocated = -1 

1194 self.scanError = vdiInfo.scanError 

1195 self.sizeLV = vdiInfo.sizeLV 

1196 self.sizeVirt = vdiInfo.sizeVirt 

1197 self.fileName = vdiInfo.lvName 

1198 self.lvActive = vdiInfo.lvActive 

1199 self.lvOpen = vdiInfo.lvOpen 

1200 self.lvReadonly = vdiInfo.lvReadonly 

1201 self._hidden = vdiInfo.hidden 

1202 self.parentUuid = vdiInfo.parentUuid 

1203 self.path = os.path.join(self.sr.path, self.fileName) 

1204 

1205 @staticmethod 

1206 def extractUuid(path): 

1207 return lvhdutil.extractUuid(path) 

1208 

1209 def getDriverName(self): 

1210 if self.raw: 

1211 return self.DRIVER_NAME_RAW 

1212 return self.DRIVER_NAME_VHD 

1213 

1214 def inflate(self, size): 

1215 """inflate the LV containing the VHD to 'size'""" 

1216 if self.raw: 

1217 return 

1218 self._activate() 

1219 self.sr.lock() 

1220 try: 

1221 lvhdutil.inflate(self.sr.journaler, self.sr.uuid, self.uuid, size) 

1222 util.fistpoint.activate("LVHDRT_inflating_the_parent", self.sr.uuid) 

1223 finally: 

1224 self.sr.unlock() 

1225 self.sizeLV = self.sr.lvmCache.getSize(self.fileName) 

1226 self._sizeVHD = -1 

1227 self._sizeAllocated = -1 

1228 

1229 def deflate(self): 

1230 """deflate the LV containing the VHD to minimum""" 

1231 if self.raw: 

1232 return 

1233 self._activate() 

1234 self.sr.lock() 

1235 try: 

1236 lvhdutil.deflate(self.sr.lvmCache, self.fileName, self.getSizeVHD()) 

1237 finally: 

1238 self.sr.unlock() 

1239 self.sizeLV = self.sr.lvmCache.getSize(self.fileName) 

1240 self._sizeVHD = -1 

1241 self._sizeAllocated = -1 

1242 

1243 def inflateFully(self): 

1244 self.inflate(lvhdutil.calcSizeVHDLV(self.sizeVirt)) 

1245 

1246 def inflateParentForCoalesce(self): 

1247 """Inflate the parent only as much as needed for the purposes of 

1248 coalescing""" 

1249 if self.parent.raw: 

1250 return 

1251 inc = self._calcExtraSpaceForCoalescing() 

1252 if inc > 0: 

1253 util.fistpoint.activate("LVHDRT_coalescing_before_inflate_grandparent", self.sr.uuid) 

1254 self.parent.inflate(self.parent.sizeLV + inc) 

1255 

1256 def updateBlockInfo(self): 

1257 if not self.raw: 

1258 return VDI.updateBlockInfo(self) 

1259 

1260 def rename(self, uuid): 

1261 oldUuid = self.uuid 

1262 oldLVName = self.fileName 

1263 VDI.rename(self, uuid) 

1264 self.fileName = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_VHD] + self.uuid 

1265 if self.raw: 

1266 self.fileName = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_RAW] + self.uuid 

1267 self.path = os.path.join(self.sr.path, self.fileName) 

1268 assert(not self.sr.lvmCache.checkLV(self.fileName)) 

1269 

1270 self.sr.lvmCache.rename(oldLVName, self.fileName) 

1271 if self.sr.lvActivator.get(oldUuid, False): 

1272 self.sr.lvActivator.replace(oldUuid, self.uuid, self.fileName, False) 

1273 

1274 ns = lvhdutil.NS_PREFIX_LVM + self.sr.uuid 

1275 (cnt, bcnt) = RefCounter.check(oldUuid, ns) 

1276 RefCounter.set(self.uuid, cnt, bcnt, ns) 

1277 RefCounter.reset(oldUuid, ns) 

1278 

1279 def delete(self): 

1280 if len(self.children) > 0: 

1281 raise util.SMException("VDI %s has children, can't delete" % \ 

1282 self.uuid) 

1283 self.sr.lock() 

1284 try: 

1285 self.sr.lvmCache.remove(self.fileName) 

1286 self.sr.forgetVDI(self.uuid) 

1287 finally: 

1288 self.sr.unlock() 

1289 RefCounter.reset(self.uuid, lvhdutil.NS_PREFIX_LVM + self.sr.uuid) 

1290 VDI.delete(self) 

1291 

1292 def getSizeVHD(self): 

1293 if self._sizeVHD == -1: 

1294 self._loadInfoSizeVHD() 

1295 return self._sizeVHD 

1296 

1297 def _loadInfoSizeVHD(self): 

1298 """Get the physical utilization of the VHD file. We do it individually 

1299 (and not using the VHD batch scanner) as an optimization: this info is 

1300 relatively expensive and we need it only for VDI's involved in 

1301 coalescing.""" 

1302 if self.raw: 

1303 return 

1304 self._activate() 

1305 self._sizeVHD = vhdutil.getSizePhys(self.path) 

1306 if self._sizeVHD <= 0: 

1307 raise util.SMException("phys size of %s = %d" % \ 

1308 (self, self._sizeVHD)) 

1309 

1310 def getAllocatedSize(self): 

1311 if self._sizeAllocated == -1: 

1312 self._loadInfoSizeAllocated() 

1313 return self._sizeAllocated 

1314 

1315 def _loadInfoSizeAllocated(self): 

1316 """ 

1317 Get the allocated size of the VHD volume. 

1318 """ 

1319 if self.raw: 

1320 return 

1321 self._activate() 

1322 self._sizeAllocated = vhdutil.getAllocatedSize(self.path) 

1323 

1324 def _loadInfoHidden(self): 

1325 if self.raw: 

1326 self._hidden = self.sr.lvmCache.getHidden(self.fileName) 

1327 else: 

1328 VDI._loadInfoHidden(self) 

1329 

1330 def _setHidden(self, hidden=True): 

1331 if self.raw: 

1332 self._hidden = None 

1333 self.sr.lvmCache.setHidden(self.fileName, hidden) 

1334 self._hidden = hidden 

1335 else: 

1336 VDI._setHidden(self, hidden) 

1337 

1338 def __str__(self): 

1339 strType = "VHD" 

1340 if self.raw: 

1341 strType = "RAW" 

1342 strHidden = "" 

1343 if self.isHidden(): 

1344 strHidden = "*" 

1345 strSizeVHD = "" 

1346 if self._sizeVHD > 0: 

1347 strSizeVHD = Util.num2str(self._sizeVHD) 

1348 strSizeAllocated = "" 

1349 if self._sizeAllocated >= 0: 

1350 strSizeAllocated = Util.num2str(self._sizeAllocated) 

1351 strActive = "n" 

1352 if self.lvActive: 

1353 strActive = "a" 

1354 if self.lvOpen: 

1355 strActive += "o" 

1356 return "%s%s[%s](%s/%s/%s/%s|%s)" % (strHidden, self.uuid[0:8], strType, 

1357 Util.num2str(self.sizeVirt), strSizeVHD, strSizeAllocated, 

1358 Util.num2str(self.sizeLV), strActive) 

1359 

1360 def validate(self, fast=False): 

1361 if not self.raw: 

1362 VDI.validate(self, fast) 

1363 

1364 def _doCoalesce(self): 

1365 """LVHD parents must first be activated, inflated, and made writable""" 

1366 try: 

1367 self._activateChain() 

1368 self.sr.lvmCache.setReadonly(self.parent.fileName, False) 

1369 self.parent.validate() 

1370 self.inflateParentForCoalesce() 

1371 VDI._doCoalesce(self) 

1372 finally: 

1373 self.parent._loadInfoSizeVHD() 

1374 self.parent.deflate() 

1375 self.sr.lvmCache.setReadonly(self.parent.fileName, True) 

1376 

1377 def _setParent(self, parent): 

1378 self._activate() 

1379 if self.lvReadonly: 

1380 self.sr.lvmCache.setReadonly(self.fileName, False) 

1381 

1382 try: 

1383 vhdutil.setParent(self.path, parent.path, parent.raw) 

1384 finally: 

1385 if self.lvReadonly: 

1386 self.sr.lvmCache.setReadonly(self.fileName, True) 

1387 self._deactivate() 

1388 self.parent = parent 

1389 self.parentUuid = parent.uuid 

1390 parent.children.append(self) 

1391 try: 

1392 self.setConfig(self.DB_VHD_PARENT, self.parentUuid) 

1393 Util.log("Updated the vhd-parent field for child %s with %s" % \ 

1394 (self.uuid, self.parentUuid)) 

1395 except: 

1396 Util.log("Failed to update the vhd-parent with %s for child %s" % \ 

1397 (self.parentUuid, self.uuid)) 

1398 

1399 def _activate(self): 

1400 self.sr.lvActivator.activate(self.uuid, self.fileName, False) 

1401 

1402 def _activateChain(self): 

1403 vdi = self 

1404 while vdi: 

1405 vdi._activate() 

1406 vdi = vdi.parent 

1407 

1408 def _deactivate(self): 

1409 self.sr.lvActivator.deactivate(self.uuid, False) 

1410 

1411 def _ensureParentActiveForRelink(self): 

1412 self.parent._activate() 

1413 

1414 def _increaseSizeVirt(self, size, atomic=True): 

1415 "ensure the virtual size of 'self' is at least 'size'" 

1416 self._activate() 

1417 if not self.raw: 

1418 VDI._increaseSizeVirt(self, size, atomic) 

1419 return 

1420 

1421 # raw VDI case 

1422 offset = self.sizeLV 

1423 if self.sizeVirt < size: 

1424 oldSize = self.sizeLV 

1425 self.sizeLV = util.roundup(lvutil.LVM_SIZE_INCREMENT, size) 

1426 Util.log(" Growing %s: %d->%d" % (self.path, oldSize, self.sizeLV)) 

1427 self.sr.lvmCache.setSize(self.fileName, self.sizeLV) 

1428 offset = oldSize 

1429 unfinishedZero = False 

1430 jval = self.sr.journaler.get(self.JRN_ZERO, self.uuid) 

1431 if jval: 

1432 unfinishedZero = True 

1433 offset = int(jval) 

1434 length = self.sizeLV - offset 

1435 if not length: 

1436 return 

1437 

1438 if unfinishedZero: 

1439 Util.log(" ==> Redoing unfinished zeroing out") 

1440 else: 

1441 self.sr.journaler.create(self.JRN_ZERO, self.uuid, \ 

1442 str(offset)) 

1443 Util.log(" Zeroing %s: from %d, %dB" % (self.path, offset, length)) 

1444 abortTest = lambda: IPCFlag(self.sr.uuid).test(FLAG_TYPE_ABORT) 

1445 func = lambda: util.zeroOut(self.path, offset, length) 

1446 Util.runAbortable(func, True, self.sr.uuid, abortTest, 

1447 VDI.POLL_INTERVAL, 0) 

1448 self.sr.journaler.remove(self.JRN_ZERO, self.uuid) 

1449 

1450 def _setSizeVirt(self, size): 

1451 """WARNING: do not call this method directly unless all VDIs in the 

1452 subtree are guaranteed to be unplugged (and remain so for the duration 

1453 of the operation): this operation is only safe for offline VHDs""" 

1454 self._activate() 

1455 jFile = lvhdutil.createVHDJournalLV(self.sr.lvmCache, self.uuid, 

1456 vhdutil.MAX_VHD_JOURNAL_SIZE) 

1457 try: 

1458 lvhdutil.setSizeVirt(self.sr.journaler, self.sr.uuid, self.uuid, 

1459 size, jFile) 

1460 finally: 

1461 lvhdutil.deleteVHDJournalLV(self.sr.lvmCache, self.uuid) 

1462 

1463 def _queryVHDBlocks(self): 

1464 self._activate() 

1465 return VDI._queryVHDBlocks(self) 

1466 

1467 def _calcExtraSpaceForCoalescing(self): 

1468 if self.parent.raw: 

1469 return 0 # raw parents are never deflated in the first place 

1470 sizeCoalesced = lvhdutil.calcSizeVHDLV(self._getCoalescedSizeData()) 

1471 Util.log("Coalesced size = %s" % Util.num2str(sizeCoalesced)) 

1472 return sizeCoalesced - self.parent.sizeLV 

1473 

1474 def _calcExtraSpaceForLeafCoalescing(self): 

1475 """How much extra space in the SR will be required to 

1476 [live-]leaf-coalesce this VDI""" 

1477 # we can deflate the leaf to minimize the space requirements 

1478 deflateDiff = self.sizeLV - lvhdutil.calcSizeLV(self.getSizeVHD()) 

1479 return self._calcExtraSpaceForCoalescing() - deflateDiff 

1480 

1481 def _calcExtraSpaceForSnapshotCoalescing(self): 

1482 return self._calcExtraSpaceForCoalescing() + \ 

1483 lvhdutil.calcSizeLV(self.getSizeVHD()) 

1484 

1485 

1486################################################################################ 

1487# 

1488# SR 

1489# 

1490class SR: 

1491 class LogFilter: 

1492 def __init__(self, sr): 

1493 self.sr = sr 

1494 self.stateLogged = False 

1495 self.prevState = {} 

1496 self.currState = {} 

1497 

1498 def logState(self): 

1499 changes = "" 

1500 self.currState.clear() 

1501 for vdi in self.sr.vdiTrees: 

1502 self.currState[vdi.uuid] = self._getTreeStr(vdi) 

1503 if not self.prevState.get(vdi.uuid) or \ 

1504 self.prevState[vdi.uuid] != self.currState[vdi.uuid]: 

1505 changes += self.currState[vdi.uuid] 

1506 

1507 for uuid in self.prevState: 

1508 if not self.currState.get(uuid): 

1509 changes += "Tree %s gone\n" % uuid 

1510 

1511 result = "SR %s (%d VDIs in %d VHD trees): " % \ 

1512 (self.sr, len(self.sr.vdis), len(self.sr.vdiTrees)) 

1513 

1514 if len(changes) > 0: 

1515 if self.stateLogged: 

1516 result += "showing only VHD trees that changed:" 

1517 result += "\n%s" % changes 

1518 else: 

1519 result += "no changes" 

1520 

1521 for line in result.split("\n"): 

1522 Util.log("%s" % line) 

1523 self.prevState.clear() 

1524 for key, val in self.currState.items(): 

1525 self.prevState[key] = val 

1526 self.stateLogged = True 

1527 

1528 def logNewVDI(self, uuid): 

1529 if self.stateLogged: 

1530 Util.log("Found new VDI when scanning: %s" % uuid) 

1531 

1532 def _getTreeStr(self, vdi, indent=8): 

1533 treeStr = "%s%s\n" % (" " * indent, vdi) 

1534 for child in vdi.children: 

1535 treeStr += self._getTreeStr(child, indent + VDI.STR_TREE_INDENT) 

1536 return treeStr 

1537 

1538 TYPE_FILE = "file" 

1539 TYPE_LVHD = "lvhd" 

1540 TYPES = [TYPE_LVHD, TYPE_FILE] 

1541 

1542 LOCK_RETRY_INTERVAL = 3 

1543 LOCK_RETRY_ATTEMPTS = 20 

1544 LOCK_RETRY_ATTEMPTS_LOCK = 100 

1545 

1546 SCAN_RETRY_ATTEMPTS = 3 

1547 

1548 JRN_CLONE = "clone" # journal entry type for the clone operation (from SM) 

1549 TMP_RENAME_PREFIX = "OLD_" 

1550 

1551 KEY_OFFLINE_COALESCE_NEEDED = "leaf_coalesce_need_offline" 

1552 KEY_OFFLINE_COALESCE_OVERRIDE = "leaf_coalesce_offline_override" 

1553 

1554 def getInstance(uuid, xapiSession, createLock=True, force=False): 

1555 xapi = XAPI(xapiSession, uuid) 

1556 type = normalizeType(xapi.srRecord["type"]) 

1557 if type == SR.TYPE_FILE: 

1558 return FileSR(uuid, xapi, createLock, force) 

1559 elif type == SR.TYPE_LVHD: 

1560 return LVHDSR(uuid, xapi, createLock, force) 

1561 raise util.SMException("SR type %s not recognized" % type) 

1562 getInstance = staticmethod(getInstance) 

1563 

1564 def __init__(self, uuid, xapi, createLock, force): 

1565 self.logFilter = self.LogFilter(self) 

1566 self.uuid = uuid 

1567 self.path = "" 

1568 self.name = "" 

1569 self.vdis = {} 

1570 self.vdiTrees = [] 

1571 self.journaler = None 

1572 self.xapi = xapi 

1573 self._locked = 0 

1574 self._srLock = None 

1575 if createLock: 1575 ↛ 1576line 1575 didn't jump to line 1576, because the condition on line 1575 was never true

1576 self._srLock = lock.Lock(vhdutil.LOCK_TYPE_SR, self.uuid) 

1577 else: 

1578 Util.log("Requested no SR locking") 

1579 self.name = self.xapi.srRecord["name_label"] 

1580 self._failedCoalesceTargets = [] 

1581 

1582 if not self.xapi.isPluggedHere(): 

1583 if force: 1583 ↛ 1584line 1583 didn't jump to line 1584, because the condition on line 1583 was never true

1584 Util.log("SR %s not attached on this host, ignoring" % uuid) 

1585 else: 

1586 if not self.wait_for_plug(): 

1587 raise util.SMException("SR %s not attached on this host" % uuid) 

1588 

1589 if force: 1589 ↛ 1590line 1589 didn't jump to line 1590, because the condition on line 1589 was never true

1590 Util.log("Not checking if we are Master (SR %s)" % uuid) 

1591 elif not self.xapi.isMaster(): 1591 ↛ 1592line 1591 didn't jump to line 1592, because the condition on line 1591 was never true

1592 raise util.SMException("This host is NOT master, will not run") 

1593 

1594 self.no_space_candidates = {} 

1595 

1596 def msg_cleared(self, xapi_session, msg_ref): 

1597 try: 

1598 msg = xapi_session.xenapi.message.get_record(msg_ref) 

1599 except XenAPI.Failure: 

1600 return True 

1601 

1602 return msg is None 

1603 

1604 def check_no_space_candidates(self): 

1605 xapi_session = self.xapi.getSession() 

1606 

1607 msg_id = self.xapi.srRecord["sm_config"].get(VDI.DB_GC_NO_SPACE) 

1608 if self.no_space_candidates: 

1609 if msg_id is None or self.msg_cleared(xapi_session, msg_id): 

1610 util.SMlog("Could not coalesce due to a lack of space " 

1611 f"in SR {self.uuid}") 

1612 msg_body = ("Unable to perform data coalesce due to a lack " 

1613 f"of space in SR {self.uuid}") 

1614 msg_id = xapi_session.xenapi.message.create( 

1615 'SM_GC_NO_SPACE', 

1616 3, 

1617 "SR", 

1618 self.uuid, 

1619 msg_body) 

1620 xapi_session.xenapi.SR.remove_from_sm_config( 

1621 self.xapi.srRef, VDI.DB_GC_NO_SPACE) 

1622 xapi_session.xenapi.SR.add_to_sm_config( 

1623 self.xapi.srRef, VDI.DB_GC_NO_SPACE, msg_id) 

1624 

1625 for candidate in self.no_space_candidates.values(): 

1626 candidate.setConfig(VDI.DB_GC_NO_SPACE, msg_id) 

1627 elif msg_id is not None: 

1628 # Everything was coalescable, remove the message 

1629 xapi_session.xenapi.SR.remove_from_sm_config(self.xapi.srRef, VDI.DB_GC_NO_SPACE) 

1630 xapi_session.xenapi.message.destroy(msg_id) 

1631 

1632 def clear_no_space_msg(self, vdi): 

1633 msg_id = None 

1634 try: 

1635 msg_id = vdi.getConfig(VDI.DB_GC_NO_SPACE) 

1636 except XenAPI.Failure: 

1637 pass 

1638 

1639 self.no_space_candidates.pop(vdi.uuid, None) 

1640 if msg_id is not None: 1640 ↛ exitline 1640 didn't return from function 'clear_no_space_msg', because the condition on line 1640 was never false

1641 vdi.delConfig(VDI.DB_GC_NO_SPACE) 

1642 

1643 

1644 def wait_for_plug(self): 

1645 for _ in range(1, 10): 

1646 time.sleep(2) 

1647 if self.xapi.isPluggedHere(): 

1648 return True 

1649 return False 

1650 

1651 def gcEnabled(self, refresh=True): 

1652 if refresh: 

1653 self.xapi.srRecord = \ 

1654 self.xapi.session.xenapi.SR.get_record(self.xapi._srRef) 

1655 if self.xapi.srRecord["other_config"].get(VDI.DB_GC) == "false": 

1656 Util.log("GC is disabled for this SR, abort") 

1657 return False 

1658 return True 

1659 

1660 def scan(self, force=False): 

1661 """Scan the SR and load VDI info for each VDI. If called repeatedly, 

1662 update VDI objects if they already exist""" 

1663 pass # abstract 

1664 

1665 def scanLocked(self, force=False): 

1666 self.lock() 

1667 try: 

1668 self.scan(force) 

1669 finally: 

1670 self.unlock() 

1671 

1672 def getVDI(self, uuid): 

1673 return self.vdis.get(uuid) 

1674 

1675 def hasWork(self): 

1676 if len(self.findGarbage()) > 0: 

1677 return True 

1678 if self.findCoalesceable(): 

1679 return True 

1680 if self.findLeafCoalesceable(): 

1681 return True 

1682 if self.needUpdateBlockInfo(): 

1683 return True 

1684 return False 

1685 

1686 def findCoalesceable(self): 

1687 """Find a coalesceable VDI. Return a vdi that should be coalesced 

1688 (choosing one among all coalesceable candidates according to some 

1689 criteria) or None if there is no VDI that could be coalesced""" 

1690 

1691 candidates = [] 

1692 

1693 srSwitch = self.xapi.srRecord["other_config"].get(VDI.DB_COALESCE) 

1694 if srSwitch == "false": 

1695 Util.log("Coalesce disabled for this SR") 

1696 return candidates 

1697 

1698 # finish any VDI for which a relink journal entry exists first 

1699 journals = self.journaler.getAll(VDI.JRN_RELINK) 

1700 for uuid in journals: 

1701 vdi = self.getVDI(uuid) 

1702 if vdi and vdi not in self._failedCoalesceTargets: 

1703 return vdi 

1704 

1705 for vdi in self.vdis.values(): 

1706 if vdi.isCoalesceable() and vdi not in self._failedCoalesceTargets: 

1707 candidates.append(vdi) 

1708 Util.log("%s is coalescable" % vdi.uuid) 

1709 

1710 self.xapi.update_task_progress("coalescable", len(candidates)) 

1711 

1712 # pick one in the tallest tree 

1713 treeHeight = dict() 

1714 for c in candidates: 

1715 height = c.getTreeRoot().getTreeHeight() 

1716 if treeHeight.get(height): 

1717 treeHeight[height].append(c) 

1718 else: 

1719 treeHeight[height] = [c] 

1720 

1721 freeSpace = self.getFreeSpace() 

1722 heights = list(treeHeight.keys()) 

1723 heights.sort(reverse=True) 

1724 for h in heights: 

1725 for c in treeHeight[h]: 

1726 spaceNeeded = c._calcExtraSpaceForCoalescing() 

1727 if spaceNeeded <= freeSpace: 

1728 Util.log("Coalesce candidate: %s (tree height %d)" % (c, h)) 

1729 self.clear_no_space_msg(c) 

1730 return c 

1731 else: 

1732 self.no_space_candidates[c.uuid] = c 

1733 Util.log("No space to coalesce %s (free space: %d)" % \ 

1734 (c, freeSpace)) 

1735 return None 

1736 

1737 def getSwitch(self, key): 

1738 return self.xapi.srRecord["other_config"].get(key) 

1739 

1740 def forbiddenBySwitch(self, switch, condition, fail_msg): 

1741 srSwitch = self.getSwitch(switch) 

1742 ret = False 

1743 if srSwitch: 

1744 ret = srSwitch == condition 

1745 

1746 if ret: 

1747 Util.log(fail_msg) 

1748 

1749 return ret 

1750 

1751 def leafCoalesceForbidden(self): 

1752 return (self.forbiddenBySwitch(VDI.DB_COALESCE, 

1753 "false", 

1754 "Coalesce disabled for this SR") or 

1755 self.forbiddenBySwitch(VDI.DB_LEAFCLSC, 

1756 VDI.LEAFCLSC_DISABLED, 

1757 "Leaf-coalesce disabled for this SR")) 

1758 

1759 def findLeafCoalesceable(self): 

1760 """Find leaf-coalesceable VDIs in each VHD tree""" 

1761 

1762 candidates = [] 

1763 if self.leafCoalesceForbidden(): 

1764 return candidates 

1765 

1766 self.gatherLeafCoalesceable(candidates) 

1767 

1768 self.xapi.update_task_progress("coalescable", len(candidates)) 

1769 

1770 freeSpace = self.getFreeSpace() 

1771 for candidate in candidates: 

1772 # check the space constraints to see if leaf-coalesce is actually 

1773 # feasible for this candidate 

1774 spaceNeeded = candidate._calcExtraSpaceForSnapshotCoalescing() 

1775 spaceNeededLive = spaceNeeded 

1776 if spaceNeeded > freeSpace: 

1777 spaceNeededLive = candidate._calcExtraSpaceForLeafCoalescing() 

1778 if candidate.canLiveCoalesce(self.getStorageSpeed()): 

1779 spaceNeeded = spaceNeededLive 

1780 

1781 if spaceNeeded <= freeSpace: 

1782 Util.log("Leaf-coalesce candidate: %s" % candidate) 

1783 self.clear_no_space_msg(candidate) 

1784 return candidate 

1785 else: 

1786 Util.log("No space to leaf-coalesce %s (free space: %d)" % \ 

1787 (candidate, freeSpace)) 

1788 if spaceNeededLive <= freeSpace: 

1789 Util.log("...but enough space if skip snap-coalesce") 

1790 candidate.setConfig(VDI.DB_LEAFCLSC, 

1791 VDI.LEAFCLSC_OFFLINE) 

1792 self.no_space_candidates[candidate.uuid] = candidate 

1793 

1794 return None 

1795 

1796 def gatherLeafCoalesceable(self, candidates): 

1797 for vdi in self.vdis.values(): 

1798 if not vdi.isLeafCoalesceable(): 

1799 continue 

1800 if vdi in self._failedCoalesceTargets: 

1801 continue 

1802 if vdi.getConfig(vdi.DB_ONBOOT) == vdi.ONBOOT_RESET: 

1803 Util.log("Skipping reset-on-boot %s" % vdi) 

1804 continue 

1805 if vdi.getConfig(vdi.DB_ALLOW_CACHING): 

1806 Util.log("Skipping allow_caching=true %s" % vdi) 

1807 continue 

1808 if vdi.getConfig(vdi.DB_LEAFCLSC) == vdi.LEAFCLSC_DISABLED: 

1809 Util.log("Leaf-coalesce disabled for %s" % vdi) 

1810 continue 

1811 if not (AUTO_ONLINE_LEAF_COALESCE_ENABLED or 

1812 vdi.getConfig(vdi.DB_LEAFCLSC) == vdi.LEAFCLSC_FORCE): 

1813 continue 

1814 candidates.append(vdi) 

1815 

1816 def coalesce(self, vdi, dryRun=False): 

1817 """Coalesce vdi onto parent""" 

1818 Util.log("Coalescing %s -> %s" % (vdi, vdi.parent)) 

1819 if dryRun: 1819 ↛ 1820line 1819 didn't jump to line 1820, because the condition on line 1819 was never true

1820 return 

1821 

1822 try: 

1823 self._coalesce(vdi) 

1824 except util.SMException as e: 

1825 if isinstance(e, AbortException): 1825 ↛ 1826line 1825 didn't jump to line 1826, because the condition on line 1825 was never true

1826 self.cleanup() 

1827 raise 

1828 else: 

1829 self._failedCoalesceTargets.append(vdi) 

1830 Util.logException("coalesce") 

1831 Util.log("Coalesce failed, skipping") 

1832 self.cleanup() 

1833 

1834 def coalesceLeaf(self, vdi, dryRun=False): 

1835 """Leaf-coalesce vdi onto parent""" 

1836 Util.log("Leaf-coalescing %s -> %s" % (vdi, vdi.parent)) 

1837 if dryRun: 

1838 return 

1839 

1840 try: 

1841 uuid = vdi.uuid 

1842 try: 

1843 # "vdi" object will no longer be valid after this call 

1844 self._coalesceLeaf(vdi) 

1845 finally: 

1846 vdi = self.getVDI(uuid) 

1847 if vdi: 

1848 vdi.delConfig(vdi.DB_LEAFCLSC) 

1849 except AbortException: 

1850 self.cleanup() 

1851 raise 

1852 except (util.SMException, XenAPI.Failure) as e: 

1853 self._failedCoalesceTargets.append(vdi) 

1854 Util.logException("leaf-coalesce") 

1855 Util.log("Leaf-coalesce failed on %s, skipping" % vdi) 

1856 self.cleanup() 

1857 

1858 def garbageCollect(self, dryRun=False): 

1859 vdiList = self.findGarbage() 

1860 Util.log("Found %d VDIs for deletion:" % len(vdiList)) 

1861 for vdi in vdiList: 

1862 Util.log(" %s" % vdi) 

1863 if not dryRun: 

1864 self.deleteVDIs(vdiList) 

1865 self.cleanupJournals(dryRun) 

1866 

1867 def findGarbage(self): 

1868 vdiList = [] 

1869 for vdi in self.vdiTrees: 

1870 vdiList.extend(vdi.getAllPrunable()) 

1871 return vdiList 

1872 

1873 def deleteVDIs(self, vdiList): 

1874 for vdi in vdiList: 

1875 if IPCFlag(self.uuid).test(FLAG_TYPE_ABORT): 

1876 raise AbortException("Aborting due to signal") 

1877 Util.log("Deleting unlinked VDI %s" % vdi) 

1878 self.deleteVDI(vdi) 

1879 

1880 def deleteVDI(self, vdi): 

1881 assert(len(vdi.children) == 0) 

1882 del self.vdis[vdi.uuid] 

1883 if vdi.parent: 1883 ↛ 1885line 1883 didn't jump to line 1885, because the condition on line 1883 was never false

1884 vdi.parent.children.remove(vdi) 

1885 if vdi in self.vdiTrees: 1885 ↛ 1886line 1885 didn't jump to line 1886, because the condition on line 1885 was never true

1886 self.vdiTrees.remove(vdi) 

1887 vdi.delete() 

1888 

1889 def forgetVDI(self, vdiUuid): 

1890 self.xapi.forgetVDI(self.uuid, vdiUuid) 

1891 

1892 def pauseVDIs(self, vdiList): 

1893 paused = [] 

1894 failed = False 

1895 for vdi in vdiList: 

1896 try: 

1897 vdi.pause() 

1898 paused.append(vdi) 

1899 except: 

1900 Util.logException("pauseVDIs") 

1901 failed = True 

1902 break 

1903 

1904 if failed: 

1905 self.unpauseVDIs(paused) 

1906 raise util.SMException("Failed to pause VDIs") 

1907 

1908 def unpauseVDIs(self, vdiList): 

1909 failed = False 

1910 for vdi in vdiList: 

1911 try: 

1912 vdi.unpause() 

1913 except: 

1914 Util.log("ERROR: Failed to unpause VDI %s" % vdi) 

1915 failed = True 

1916 if failed: 

1917 raise util.SMException("Failed to unpause VDIs") 

1918 

1919 def getFreeSpace(self): 

1920 return 0 

1921 

1922 def cleanup(self): 

1923 Util.log("In cleanup") 

1924 return 

1925 

1926 def __str__(self): 

1927 if self.name: 

1928 ret = "%s ('%s')" % (self.uuid[0:4], self.name) 

1929 else: 

1930 ret = "%s" % self.uuid 

1931 return ret 

1932 

1933 def lock(self): 

1934 """Acquire the SR lock. Nested acquire()'s are ok. Check for Abort 

1935 signal to avoid deadlocking (trying to acquire the SR lock while the 

1936 lock is held by a process that is trying to abort us)""" 

1937 if not self._srLock: 

1938 return 

1939 

1940 if self._locked == 0: 

1941 abortFlag = IPCFlag(self.uuid) 

1942 for i in range(SR.LOCK_RETRY_ATTEMPTS_LOCK): 

1943 if self._srLock.acquireNoblock(): 

1944 self._locked += 1 

1945 return 

1946 if abortFlag.test(FLAG_TYPE_ABORT): 

1947 raise AbortException("Abort requested") 

1948 time.sleep(SR.LOCK_RETRY_INTERVAL) 

1949 raise util.SMException("Unable to acquire the SR lock") 

1950 

1951 self._locked += 1 

1952 

1953 def unlock(self): 

1954 if not self._srLock: 1954 ↛ 1956line 1954 didn't jump to line 1956, because the condition on line 1954 was never false

1955 return 

1956 assert(self._locked > 0) 

1957 self._locked -= 1 

1958 if self._locked == 0: 

1959 self._srLock.release() 

1960 

1961 def needUpdateBlockInfo(self): 

1962 for vdi in self.vdis.values(): 

1963 if vdi.scanError or len(vdi.children) == 0: 

1964 continue 

1965 if not vdi.getConfig(vdi.DB_VHD_BLOCKS): 

1966 return True 

1967 return False 

1968 

1969 def updateBlockInfo(self): 

1970 for vdi in self.vdis.values(): 

1971 if vdi.scanError or len(vdi.children) == 0: 

1972 continue 

1973 if not vdi.getConfig(vdi.DB_VHD_BLOCKS): 

1974 vdi.updateBlockInfo() 

1975 

1976 def cleanupCoalesceJournals(self): 

1977 """Remove stale coalesce VDI indicators""" 

1978 entries = self.journaler.getAll(VDI.JRN_COALESCE) 

1979 for uuid, jval in entries.items(): 

1980 self.journaler.remove(VDI.JRN_COALESCE, uuid) 

1981 

1982 def cleanupJournals(self, dryRun=False): 

1983 """delete journal entries for non-existing VDIs""" 

1984 for t in [LVHDVDI.JRN_ZERO, VDI.JRN_RELINK, SR.JRN_CLONE]: 

1985 entries = self.journaler.getAll(t) 

1986 for uuid, jval in entries.items(): 

1987 if self.getVDI(uuid): 

1988 continue 

1989 if t == SR.JRN_CLONE: 

1990 baseUuid, clonUuid = jval.split("_") 

1991 if self.getVDI(baseUuid): 

1992 continue 

1993 Util.log(" Deleting stale '%s' journal entry for %s " 

1994 "(%s)" % (t, uuid, jval)) 

1995 if not dryRun: 

1996 self.journaler.remove(t, uuid) 

1997 

1998 def cleanupCache(self, maxAge=-1): 

1999 return 0 

2000 

2001 def _coalesce(self, vdi): 

2002 if self.journaler.get(vdi.JRN_RELINK, vdi.uuid): 2002 ↛ 2005line 2002 didn't jump to line 2005, because the condition on line 2002 was never true

2003 # this means we had done the actual coalescing already and just 

2004 # need to finish relinking and/or refreshing the children 

2005 Util.log("==> Coalesce apparently already done: skipping") 

2006 

2007 # The parent volume must be active for the parent change to occur. 

2008 # The parent volume may become inactive if the host is rebooted. 

2009 vdi._ensureParentActiveForRelink() 

2010 else: 

2011 # JRN_COALESCE is used to check which VDI is being coalesced in 

2012 # order to decide whether to abort the coalesce. We remove the 

2013 # journal as soon as the VHD coalesce step is done, because we 

2014 # don't expect the rest of the process to take long 

2015 self.journaler.create(vdi.JRN_COALESCE, vdi.uuid, "1") 

2016 vdi._doCoalesce() 

2017 self.journaler.remove(vdi.JRN_COALESCE, vdi.uuid) 

2018 

2019 util.fistpoint.activate("LVHDRT_before_create_relink_journal", self.uuid) 

2020 

2021 # we now need to relink the children: lock the SR to prevent ops 

2022 # like SM.clone from manipulating the VDIs we'll be relinking and 

2023 # rescan the SR first in case the children changed since the last 

2024 # scan 

2025 self.journaler.create(vdi.JRN_RELINK, vdi.uuid, "1") 

2026 

2027 self.lock() 

2028 try: 

2029 vdi.parent._tagChildrenForRelink() 

2030 self.scan() 

2031 vdi._relinkSkip() 

2032 finally: 

2033 self.unlock() 

2034 # Reload the children to leave things consistent 

2035 vdi.parent._reloadChildren(vdi) 

2036 

2037 self.journaler.remove(vdi.JRN_RELINK, vdi.uuid) 

2038 self.deleteVDI(vdi) 

2039 

2040 class CoalesceTracker: 

2041 GRACE_ITERATIONS = 2 

2042 MAX_ITERATIONS_NO_PROGRESS = 3 

2043 MAX_ITERATIONS = 20 

2044 MAX_INCREASE_FROM_MINIMUM = 1.2 

2045 HISTORY_STRING = "Iteration: {its} -- Initial size {initSize}" \ 

2046 " --> Final size {finSize}" 

2047 

2048 def __init__(self, sr): 

2049 self.itsNoProgress = 0 

2050 self.its = 0 

2051 self.minSize = float("inf") 

2052 self._history = [] 

2053 self.reason = "" 

2054 self.startSize = None 

2055 self.finishSize = None 

2056 self.sr = sr 

2057 self.grace_remaining = self.GRACE_ITERATIONS 

2058 

2059 @property 

2060 def history(self): 

2061 return [x['msg'] for x in self._history] 

2062 

2063 def moving_average(self): 

2064 """ 

2065 Calculate a three point moving average 

2066 """ 

2067 assert len(self._history) >= 3 

2068 

2069 mv_average = sum([x['finalsize'] for x in self._history]) / len(self._history) 

2070 util.SMlog(f'Calculated moving average as {mv_average}') 

2071 return mv_average 

2072 

2073 def abortCoalesce(self, prevSize, curSize): 

2074 self.its += 1 

2075 self._history.append( 

2076 { 

2077 'finalsize': curSize, 

2078 'msg': self.HISTORY_STRING.format(its=self.its, 

2079 initSize=prevSize, 

2080 finSize=curSize) 

2081 } 

2082 ) 

2083 

2084 self.finishSize = curSize 

2085 

2086 if self.startSize is None: 

2087 self.startSize = prevSize 

2088 

2089 if curSize < self.minSize: 

2090 self.minSize = curSize 

2091 

2092 if prevSize < self.minSize: 

2093 self.minSize = prevSize 

2094 

2095 if self.its < 4: 

2096 # Perform at least three iterations 

2097 return False 

2098 

2099 if prevSize >= curSize or curSize < self.moving_average(): 

2100 # We made progress 

2101 return False 

2102 else: 

2103 self.itsNoProgress += 1 

2104 Util.log("No progress, attempt:" 

2105 " {attempt}".format(attempt=self.itsNoProgress)) 

2106 util.fistpoint.activate("cleanup_tracker_no_progress", self.sr.uuid) 

2107 

2108 if self.its > self.MAX_ITERATIONS: 

2109 max = self.MAX_ITERATIONS 

2110 self.reason = \ 

2111 "Max iterations ({max}) exceeded".format(max=max) 

2112 return True 

2113 

2114 if self.itsNoProgress > self.MAX_ITERATIONS_NO_PROGRESS: 

2115 max = self.MAX_ITERATIONS_NO_PROGRESS 

2116 self.reason = \ 

2117 "No progress made for {max} iterations".format(max=max) 

2118 return True 

2119 

2120 maxSizeFromMin = self.MAX_INCREASE_FROM_MINIMUM * self.minSize 

2121 if curSize > maxSizeFromMin: 

2122 self.grace_remaining -= 1 

2123 if self.grace_remaining == 0: 

2124 self.reason = "Unexpected bump in size," \ 

2125 " compared to minimum achieved" 

2126 

2127 return True 

2128 

2129 return False 

2130 

2131 def printSizes(self): 

2132 Util.log("Starting size was {size}" 

2133 .format(size=self.startSize)) 

2134 Util.log("Final size was {size}" 

2135 .format(size=self.finishSize)) 

2136 Util.log("Minimum size achieved was {size}" 

2137 .format(size=self.minSize)) 

2138 

2139 def printReasoning(self): 

2140 Util.log("Aborted coalesce") 

2141 for hist in self.history: 

2142 Util.log(hist) 

2143 Util.log(self.reason) 

2144 self.printSizes() 

2145 

2146 def printSummary(self): 

2147 if self.its == 0: 

2148 return 

2149 

2150 if self.reason: 2150 ↛ 2151line 2150 didn't jump to line 2151, because the condition on line 2150 was never true

2151 Util.log("Aborted coalesce") 

2152 Util.log(self.reason) 

2153 else: 

2154 Util.log("Coalesce summary") 

2155 

2156 Util.log(f"Performed {self.its} iterations") 

2157 self.printSizes() 

2158 

2159 

2160 def _coalesceLeaf(self, vdi): 

2161 """Leaf-coalesce VDI vdi. Return true if we succeed, false if we cannot 

2162 complete due to external changes, namely vdi_delete and vdi_snapshot 

2163 that alter leaf-coalescibility of vdi""" 

2164 tracker = self.CoalesceTracker(self) 

2165 while not vdi.canLiveCoalesce(self.getStorageSpeed()): 

2166 prevSizeVHD = vdi.getSizeVHD() 

2167 if not self._snapshotCoalesce(vdi): 2167 ↛ 2168line 2167 didn't jump to line 2168, because the condition on line 2167 was never true

2168 return False 

2169 if tracker.abortCoalesce(prevSizeVHD, vdi.getSizeVHD()): 

2170 tracker.printReasoning() 

2171 raise util.SMException("VDI {uuid} could not be coalesced" 

2172 .format(uuid=vdi.uuid)) 

2173 tracker.printSummary() 

2174 return self._liveLeafCoalesce(vdi) 

2175 

2176 def calcStorageSpeed(self, startTime, endTime, vhdSize): 

2177 speed = None 

2178 total_time = endTime - startTime 

2179 if total_time > 0: 

2180 speed = float(vhdSize) / float(total_time) 

2181 return speed 

2182 

2183 def writeSpeedToFile(self, speed): 

2184 content = [] 

2185 speedFile = None 

2186 path = SPEED_LOG_ROOT.format(uuid=self.uuid) 

2187 self.lock() 

2188 try: 

2189 Util.log("Writing to file: {myfile}".format(myfile=path)) 

2190 lines = "" 

2191 if not os.path.isfile(path): 

2192 lines = str(speed) + "\n" 

2193 else: 

2194 speedFile = open(path, "r+") 

2195 content = speedFile.readlines() 

2196 content.append(str(speed) + "\n") 

2197 if len(content) > N_RUNNING_AVERAGE: 

2198 del content[0] 

2199 lines = "".join(content) 

2200 

2201 util.atomicFileWrite(path, VAR_RUN, lines) 

2202 finally: 

2203 if speedFile is not None: 

2204 speedFile.close() 

2205 Util.log("Closing file: {myfile}".format(myfile=path)) 

2206 self.unlock() 

2207 

2208 def recordStorageSpeed(self, startTime, endTime, vhdSize): 

2209 speed = self.calcStorageSpeed(startTime, endTime, vhdSize) 

2210 if speed is None: 

2211 return 

2212 

2213 self.writeSpeedToFile(speed) 

2214 

2215 def getStorageSpeed(self): 

2216 speedFile = None 

2217 path = SPEED_LOG_ROOT.format(uuid=self.uuid) 

2218 self.lock() 

2219 try: 

2220 speed = None 

2221 if os.path.isfile(path): 

2222 speedFile = open(path) 

2223 content = speedFile.readlines() 

2224 try: 

2225 content = [float(i) for i in content] 

2226 except ValueError: 

2227 Util.log("Something bad in the speed log:{log}". 

2228 format(log=speedFile.readlines())) 

2229 return speed 

2230 

2231 if len(content): 

2232 speed = sum(content) / float(len(content)) 

2233 if speed <= 0: 2233 ↛ 2235line 2233 didn't jump to line 2235, because the condition on line 2233 was never true

2234 # Defensive, should be impossible. 

2235 Util.log("Bad speed: {speed} calculated for SR: {uuid}". 

2236 format(speed=speed, uuid=self.uuid)) 

2237 speed = None 

2238 else: 

2239 Util.log("Speed file empty for SR: {uuid}". 

2240 format(uuid=self.uuid)) 

2241 else: 

2242 Util.log("Speed log missing for SR: {uuid}". 

2243 format(uuid=self.uuid)) 

2244 return speed 

2245 finally: 

2246 if not (speedFile is None): 

2247 speedFile.close() 

2248 self.unlock() 

2249 

2250 def _snapshotCoalesce(self, vdi): 

2251 # Note that because we are not holding any locks here, concurrent SM 

2252 # operations may change this tree under our feet. In particular, vdi 

2253 # can be deleted, or it can be snapshotted. 

2254 assert(AUTO_ONLINE_LEAF_COALESCE_ENABLED) 

2255 Util.log("Single-snapshotting %s" % vdi) 

2256 util.fistpoint.activate("LVHDRT_coaleaf_delay_1", self.uuid) 

2257 try: 

2258 ret = self.xapi.singleSnapshotVDI(vdi) 

2259 Util.log("Single-snapshot returned: %s" % ret) 

2260 except XenAPI.Failure as e: 

2261 if util.isInvalidVDI(e): 

2262 Util.log("The VDI appears to have been concurrently deleted") 

2263 return False 

2264 raise 

2265 self.scanLocked() 

2266 tempSnap = vdi.parent 

2267 if not tempSnap.isCoalesceable(): 

2268 Util.log("The VDI appears to have been concurrently snapshotted") 

2269 return False 

2270 Util.log("Coalescing parent %s" % tempSnap) 

2271 util.fistpoint.activate("LVHDRT_coaleaf_delay_2", self.uuid) 

2272 vhdSize = vdi.getSizeVHD() 

2273 self._coalesce(tempSnap) 

2274 if not vdi.isLeafCoalesceable(): 

2275 Util.log("The VDI tree appears to have been altered since") 

2276 return False 

2277 return True 

2278 

2279 def _liveLeafCoalesce(self, vdi): 

2280 util.fistpoint.activate("LVHDRT_coaleaf_delay_3", self.uuid) 

2281 self.lock() 

2282 try: 

2283 self.scan() 

2284 if not self.getVDI(vdi.uuid): 

2285 Util.log("The VDI appears to have been deleted meanwhile") 

2286 return False 

2287 if not vdi.isLeafCoalesceable(): 

2288 Util.log("The VDI is no longer leaf-coalesceable") 

2289 return False 

2290 

2291 uuid = vdi.uuid 

2292 vdi.pause(failfast=True) 

2293 try: 

2294 try: 

2295 # "vdi" object will no longer be valid after this call 

2296 self._doCoalesceLeaf(vdi) 

2297 except: 

2298 Util.logException("_doCoalesceLeaf") 

2299 self._handleInterruptedCoalesceLeaf() 

2300 raise 

2301 finally: 

2302 vdi = self.getVDI(uuid) 

2303 if vdi: 

2304 vdi.ensureUnpaused() 

2305 vdiOld = self.getVDI(self.TMP_RENAME_PREFIX + uuid) 

2306 if vdiOld: 

2307 util.fistpoint.activate("LVHDRT_coaleaf_before_delete", self.uuid) 

2308 self.deleteVDI(vdiOld) 

2309 util.fistpoint.activate("LVHDRT_coaleaf_after_delete", self.uuid) 

2310 finally: 

2311 self.cleanup() 

2312 self.unlock() 

2313 self.logFilter.logState() 

2314 return True 

2315 

2316 def _doCoalesceLeaf(self, vdi): 

2317 """Actual coalescing of a leaf VDI onto parent. Must be called in an 

2318 offline/atomic context""" 

2319 self.journaler.create(VDI.JRN_LEAF, vdi.uuid, vdi.parent.uuid) 

2320 self._prepareCoalesceLeaf(vdi) 

2321 vdi.parent._setHidden(False) 

2322 vdi.parent._increaseSizeVirt(vdi.sizeVirt, False) 

2323 vdi.validate(True) 

2324 vdi.parent.validate(True) 

2325 util.fistpoint.activate("LVHDRT_coaleaf_before_coalesce", self.uuid) 

2326 timeout = vdi.LIVE_LEAF_COALESCE_TIMEOUT 

2327 if vdi.getConfig(vdi.DB_LEAFCLSC) == vdi.LEAFCLSC_FORCE: 2327 ↛ 2328line 2327 didn't jump to line 2328, because the condition on line 2327 was never true

2328 Util.log("Leaf-coalesce forced, will not use timeout") 

2329 timeout = 0 

2330 vdi._coalesceVHD(timeout) 

2331 util.fistpoint.activate("LVHDRT_coaleaf_after_coalesce", self.uuid) 

2332 vdi.parent.validate(True) 

2333 #vdi._verifyContents(timeout / 2) 

2334 

2335 # rename 

2336 vdiUuid = vdi.uuid 

2337 oldName = vdi.fileName 

2338 origParentUuid = vdi.parent.uuid 

2339 vdi.rename(self.TMP_RENAME_PREFIX + vdiUuid) 

2340 util.fistpoint.activate("LVHDRT_coaleaf_one_renamed", self.uuid) 

2341 vdi.parent.rename(vdiUuid) 

2342 util.fistpoint.activate("LVHDRT_coaleaf_both_renamed", self.uuid) 

2343 self._updateSlavesOnRename(vdi.parent, oldName, origParentUuid) 

2344 

2345 # Note that "vdi.parent" is now the single remaining leaf and "vdi" is 

2346 # garbage 

2347 

2348 # update the VDI record 

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

2350 vdi.parent.setConfig(VDI.DB_VDI_TYPE, vhdutil.VDI_TYPE_RAW) 

2351 vdi.parent.delConfig(VDI.DB_VHD_BLOCKS) 

2352 util.fistpoint.activate("LVHDRT_coaleaf_after_vdirec", self.uuid) 

2353 

2354 self._updateNode(vdi) 

2355 

2356 # delete the obsolete leaf & inflate the parent (in that order, to 

2357 # minimize free space requirements) 

2358 parent = vdi.parent 

2359 vdi._setHidden(True) 

2360 vdi.parent.children = [] 

2361 vdi.parent = None 

2362 

2363 if parent.parent is None: 

2364 parent.delConfig(VDI.DB_VHD_PARENT) 

2365 

2366 extraSpace = self._calcExtraSpaceNeeded(vdi, parent) 

2367 freeSpace = self.getFreeSpace() 

2368 if freeSpace < extraSpace: 2368 ↛ 2371line 2368 didn't jump to line 2371, because the condition on line 2368 was never true

2369 # don't delete unless we need the space: deletion is time-consuming 

2370 # because it requires contacting the slaves, and we're paused here 

2371 util.fistpoint.activate("LVHDRT_coaleaf_before_delete", self.uuid) 

2372 self.deleteVDI(vdi) 

2373 util.fistpoint.activate("LVHDRT_coaleaf_after_delete", self.uuid) 

2374 

2375 util.fistpoint.activate("LVHDRT_coaleaf_before_remove_j", self.uuid) 

2376 self.journaler.remove(VDI.JRN_LEAF, vdiUuid) 

2377 

2378 self.forgetVDI(origParentUuid) 

2379 self._finishCoalesceLeaf(parent) 

2380 self._updateSlavesOnResize(parent) 

2381 

2382 def _calcExtraSpaceNeeded(self, child, parent): 

2383 assert(not parent.raw) # raw parents not supported 

2384 extra = child.getSizeVHD() - parent.getSizeVHD() 

2385 if extra < 0: 2385 ↛ 2386line 2385 didn't jump to line 2386, because the condition on line 2385 was never true

2386 extra = 0 

2387 return extra 

2388 

2389 def _prepareCoalesceLeaf(self, vdi): 

2390 pass 

2391 

2392 def _updateNode(self, vdi): 

2393 pass 

2394 

2395 def _finishCoalesceLeaf(self, parent): 

2396 pass 

2397 

2398 def _updateSlavesOnUndoLeafCoalesce(self, parent, child): 

2399 pass 

2400 

2401 def _updateSlavesOnRename(self, vdi, oldName, origParentUuid): 

2402 pass 

2403 

2404 def _updateSlavesOnResize(self, vdi): 

2405 pass 

2406 

2407 def _removeStaleVDIs(self, uuidsPresent): 

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

2409 if not uuid in uuidsPresent: 

2410 Util.log("VDI %s disappeared since last scan" % \ 

2411 self.vdis[uuid]) 

2412 del self.vdis[uuid] 

2413 

2414 def _handleInterruptedCoalesceLeaf(self): 

2415 """An interrupted leaf-coalesce operation may leave the VHD tree in an 

2416 inconsistent state. If the old-leaf VDI is still present, we revert the 

2417 operation (in case the original error is persistent); otherwise we must 

2418 finish the operation""" 

2419 # abstract 

2420 pass 

2421 

2422 def _buildTree(self, force): 

2423 self.vdiTrees = [] 

2424 for vdi in self.vdis.values(): 

2425 if vdi.parentUuid: 

2426 parent = self.getVDI(vdi.parentUuid) 

2427 if not parent: 

2428 if vdi.uuid.startswith(self.TMP_RENAME_PREFIX): 

2429 self.vdiTrees.append(vdi) 

2430 continue 

2431 if force: 

2432 Util.log("ERROR: Parent VDI %s not found! (for %s)" % \ 

2433 (vdi.parentUuid, vdi.uuid)) 

2434 self.vdiTrees.append(vdi) 

2435 continue 

2436 else: 

2437 raise util.SMException("Parent VDI %s of %s not " \ 

2438 "found" % (vdi.parentUuid, vdi.uuid)) 

2439 vdi.parent = parent 

2440 parent.children.append(vdi) 

2441 else: 

2442 self.vdiTrees.append(vdi) 

2443 

2444 

2445class FileSR(SR): 

2446 TYPE = SR.TYPE_FILE 

2447 CACHE_FILE_EXT = ".vhdcache" 

2448 # cache cleanup actions 

2449 CACHE_ACTION_KEEP = 0 

2450 CACHE_ACTION_REMOVE = 1 

2451 CACHE_ACTION_REMOVE_IF_INACTIVE = 2 

2452 

2453 def __init__(self, uuid, xapi, createLock, force): 

2454 SR.__init__(self, uuid, xapi, createLock, force) 

2455 self.path = "/run/sr-mount/%s" % self.uuid 

2456 self.journaler = fjournaler.Journaler(self.path) 

2457 

2458 def scan(self, force=False): 

2459 if not util.pathexists(self.path): 

2460 raise util.SMException("directory %s not found!" % self.uuid) 

2461 vhds = self._scan(force) 

2462 for uuid, vhdInfo in vhds.items(): 

2463 vdi = self.getVDI(uuid) 

2464 if not vdi: 

2465 self.logFilter.logNewVDI(uuid) 

2466 vdi = FileVDI(self, uuid, False) 

2467 self.vdis[uuid] = vdi 

2468 vdi.load(vhdInfo) 

2469 uuidsPresent = list(vhds.keys()) 

2470 rawList = [x for x in os.listdir(self.path) if x.endswith(vhdutil.FILE_EXTN_RAW)] 

2471 for rawName in rawList: 

2472 uuid = FileVDI.extractUuid(rawName) 

2473 uuidsPresent.append(uuid) 

2474 vdi = self.getVDI(uuid) 

2475 if not vdi: 

2476 self.logFilter.logNewVDI(uuid) 

2477 vdi = FileVDI(self, uuid, True) 

2478 self.vdis[uuid] = vdi 

2479 self._removeStaleVDIs(uuidsPresent) 

2480 self._buildTree(force) 

2481 self.logFilter.logState() 

2482 self._handleInterruptedCoalesceLeaf() 

2483 

2484 def getFreeSpace(self): 

2485 return util.get_fs_size(self.path) - util.get_fs_utilisation(self.path) 

2486 

2487 def deleteVDIs(self, vdiList): 

2488 rootDeleted = False 

2489 for vdi in vdiList: 

2490 if not vdi.parent: 

2491 rootDeleted = True 

2492 break 

2493 SR.deleteVDIs(self, vdiList) 

2494 if self.xapi.srRecord["type"] == "nfs" and rootDeleted: 

2495 self.xapi.markCacheSRsDirty() 

2496 

2497 def cleanupCache(self, maxAge=-1): 

2498 """Clean up IntelliCache cache files. Caches for leaf nodes are 

2499 removed when the leaf node no longer exists or its allow-caching 

2500 attribute is not set. Caches for parent nodes are removed when the 

2501 parent node no longer exists or it hasn't been used in more than 

2502 <maxAge> hours. 

2503 Return number of caches removed. 

2504 """ 

2505 numRemoved = 0 

2506 cacheFiles = [x for x in os.listdir(self.path) if self._isCacheFileName(x)] 

2507 Util.log("Found %d cache files" % len(cacheFiles)) 

2508 cutoff = datetime.datetime.now() - datetime.timedelta(hours=maxAge) 

2509 for cacheFile in cacheFiles: 

2510 uuid = cacheFile[:-len(self.CACHE_FILE_EXT)] 

2511 action = self.CACHE_ACTION_KEEP 

2512 rec = self.xapi.getRecordVDI(uuid) 

2513 if not rec: 

2514 Util.log("Cache %s: VDI doesn't exist" % uuid) 

2515 action = self.CACHE_ACTION_REMOVE 

2516 elif rec["managed"] and not rec["allow_caching"]: 

2517 Util.log("Cache %s: caching disabled" % uuid) 

2518 action = self.CACHE_ACTION_REMOVE 

2519 elif not rec["managed"] and maxAge >= 0: 

2520 lastAccess = datetime.datetime.fromtimestamp( \ 

2521 os.path.getatime(os.path.join(self.path, cacheFile))) 

2522 if lastAccess < cutoff: 

2523 Util.log("Cache %s: older than %d hrs" % (uuid, maxAge)) 

2524 action = self.CACHE_ACTION_REMOVE_IF_INACTIVE 

2525 

2526 if action == self.CACHE_ACTION_KEEP: 

2527 Util.log("Keeping cache %s" % uuid) 

2528 continue 

2529 

2530 lockId = uuid 

2531 parentUuid = None 

2532 if rec and rec["managed"]: 

2533 parentUuid = rec["sm_config"].get("vhd-parent") 

2534 if parentUuid: 

2535 lockId = parentUuid 

2536 

2537 cacheLock = lock.Lock(blktap2.VDI.LOCK_CACHE_SETUP, lockId) 

2538 cacheLock.acquire() 

2539 try: 

2540 if self._cleanupCache(uuid, action): 

2541 numRemoved += 1 

2542 finally: 

2543 cacheLock.release() 

2544 return numRemoved 

2545 

2546 def _cleanupCache(self, uuid, action): 

2547 assert(action != self.CACHE_ACTION_KEEP) 

2548 rec = self.xapi.getRecordVDI(uuid) 

2549 if rec and rec["allow_caching"]: 

2550 Util.log("Cache %s appears to have become valid" % uuid) 

2551 return False 

2552 

2553 fullPath = os.path.join(self.path, uuid + self.CACHE_FILE_EXT) 

2554 tapdisk = blktap2.Tapdisk.find_by_path(fullPath) 

2555 if tapdisk: 

2556 if action == self.CACHE_ACTION_REMOVE_IF_INACTIVE: 

2557 Util.log("Cache %s still in use" % uuid) 

2558 return False 

2559 Util.log("Shutting down tapdisk for %s" % fullPath) 

2560 tapdisk.shutdown() 

2561 

2562 Util.log("Deleting file %s" % fullPath) 

2563 os.unlink(fullPath) 

2564 return True 

2565 

2566 def _isCacheFileName(self, name): 

2567 return (len(name) == Util.UUID_LEN + len(self.CACHE_FILE_EXT)) and \ 

2568 name.endswith(self.CACHE_FILE_EXT) 

2569 

2570 def _scan(self, force): 

2571 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2572 error = False 

2573 pattern = os.path.join(self.path, "*%s" % vhdutil.FILE_EXTN_VHD) 

2574 vhds = vhdutil.getAllVHDs(pattern, FileVDI.extractUuid) 

2575 for uuid, vhdInfo in vhds.items(): 

2576 if vhdInfo.error: 

2577 error = True 

2578 break 

2579 if not error: 

2580 return vhds 

2581 Util.log("Scan error on attempt %d" % i) 

2582 if force: 

2583 return vhds 

2584 raise util.SMException("Scan error") 

2585 

2586 def deleteVDI(self, vdi): 

2587 self._checkSlaves(vdi) 

2588 SR.deleteVDI(self, vdi) 

2589 

2590 def _checkSlaves(self, vdi): 

2591 onlineHosts = self.xapi.getOnlineHosts() 

2592 abortFlag = IPCFlag(self.uuid) 

2593 for pbdRecord in self.xapi.getAttachedPBDs(): 

2594 hostRef = pbdRecord["host"] 

2595 if hostRef == self.xapi._hostRef: 

2596 continue 

2597 if abortFlag.test(FLAG_TYPE_ABORT): 

2598 raise AbortException("Aborting due to signal") 

2599 try: 

2600 self._checkSlave(hostRef, vdi) 

2601 except util.CommandException: 

2602 if hostRef in onlineHosts: 

2603 raise 

2604 

2605 def _checkSlave(self, hostRef, vdi): 

2606 call = (hostRef, "nfs-on-slave", "check", {'path': vdi.path}) 

2607 Util.log("Checking with slave: %s" % repr(call)) 

2608 _host = self.xapi.session.xenapi.host 

2609 text = _host.call_plugin( * call) 

2610 

2611 def _handleInterruptedCoalesceLeaf(self): 

2612 entries = self.journaler.getAll(VDI.JRN_LEAF) 

2613 for uuid, parentUuid in entries.items(): 

2614 fileList = os.listdir(self.path) 

2615 childName = uuid + vhdutil.FILE_EXTN_VHD 

2616 tmpChildName = self.TMP_RENAME_PREFIX + uuid + vhdutil.FILE_EXTN_VHD 

2617 parentName1 = parentUuid + vhdutil.FILE_EXTN_VHD 

2618 parentName2 = parentUuid + vhdutil.FILE_EXTN_RAW 

2619 parentPresent = (parentName1 in fileList or parentName2 in fileList) 

2620 if parentPresent or tmpChildName in fileList: 

2621 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

2622 else: 

2623 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

2624 self.journaler.remove(VDI.JRN_LEAF, uuid) 

2625 vdi = self.getVDI(uuid) 

2626 if vdi: 

2627 vdi.ensureUnpaused() 

2628 

2629 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

2630 Util.log("*** UNDO LEAF-COALESCE") 

2631 parent = self.getVDI(parentUuid) 

2632 if not parent: 

2633 parent = self.getVDI(childUuid) 

2634 if not parent: 

2635 raise util.SMException("Neither %s nor %s found" % \ 

2636 (parentUuid, childUuid)) 

2637 Util.log("Renaming parent back: %s -> %s" % (childUuid, parentUuid)) 

2638 parent.rename(parentUuid) 

2639 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_rename", self.uuid) 

2640 

2641 child = self.getVDI(childUuid) 

2642 if not child: 

2643 child = self.getVDI(self.TMP_RENAME_PREFIX + childUuid) 

2644 if not child: 

2645 raise util.SMException("Neither %s nor %s found" % \ 

2646 (childUuid, self.TMP_RENAME_PREFIX + childUuid)) 

2647 Util.log("Renaming child back to %s" % childUuid) 

2648 child.rename(childUuid) 

2649 Util.log("Updating the VDI record") 

2650 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

2651 child.setConfig(VDI.DB_VDI_TYPE, vhdutil.VDI_TYPE_VHD) 

2652 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_rename2", self.uuid) 

2653 

2654 if child.isHidden(): 

2655 child._setHidden(False) 

2656 if not parent.isHidden(): 

2657 parent._setHidden(True) 

2658 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

2659 util.fistpoint.activate("LVHDRT_coaleaf_undo_end", self.uuid) 

2660 Util.log("*** leaf-coalesce undo successful") 

2661 if util.fistpoint.is_active("LVHDRT_coaleaf_stop_after_recovery"): 

2662 child.setConfig(VDI.DB_LEAFCLSC, VDI.LEAFCLSC_DISABLED) 

2663 

2664 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

2665 Util.log("*** FINISH LEAF-COALESCE") 

2666 vdi = self.getVDI(childUuid) 

2667 if not vdi: 

2668 Util.log(f"_finishInterruptedCoalesceLeaf, vdi {childUuid} not found, aborting") 

2669 raise util.SMException("VDI %s not found" % childUuid) 

2670 try: 

2671 self.forgetVDI(parentUuid) 

2672 except XenAPI.Failure: 

2673 Util.logException('_finishInterruptedCoalesceLeaf') 

2674 pass 

2675 self._updateSlavesOnResize(vdi) 

2676 util.fistpoint.activate("LVHDRT_coaleaf_finish_end", self.uuid) 

2677 Util.log("*** finished leaf-coalesce successfully") 

2678 

2679 

2680class LVHDSR(SR): 

2681 TYPE = SR.TYPE_LVHD 

2682 SUBTYPES = ["lvhdoiscsi", "lvhdohba"] 

2683 

2684 def __init__(self, uuid, xapi, createLock, force): 

2685 SR.__init__(self, uuid, xapi, createLock, force) 

2686 self.vgName = "%s%s" % (lvhdutil.VG_PREFIX, self.uuid) 

2687 self.path = os.path.join(lvhdutil.VG_LOCATION, self.vgName) 

2688 self.lvmCache = lvmcache.LVMCache(self.vgName) 

2689 self.lvActivator = LVActivator(self.uuid, self.lvmCache) 

2690 self.journaler = journaler.Journaler(self.lvmCache) 

2691 

2692 def deleteVDI(self, vdi): 

2693 if self.lvActivator.get(vdi.uuid, False): 

2694 self.lvActivator.deactivate(vdi.uuid, False) 

2695 self._checkSlaves(vdi) 

2696 SR.deleteVDI(self, vdi) 

2697 

2698 def forgetVDI(self, vdiUuid): 

2699 SR.forgetVDI(self, vdiUuid) 

2700 mdpath = os.path.join(self.path, lvutil.MDVOLUME_NAME) 

2701 LVMMetadataHandler(mdpath).deleteVdiFromMetadata(vdiUuid) 

2702 

2703 def getFreeSpace(self): 

2704 stats = lvutil._getVGstats(self.vgName) 

2705 return stats['physical_size'] - stats['physical_utilisation'] 

2706 

2707 def cleanup(self): 

2708 if not self.lvActivator.deactivateAll(): 

2709 Util.log("ERROR deactivating LVs while cleaning up") 

2710 

2711 def needUpdateBlockInfo(self): 

2712 for vdi in self.vdis.values(): 

2713 if vdi.scanError or vdi.raw or len(vdi.children) == 0: 

2714 continue 

2715 if not vdi.getConfig(vdi.DB_VHD_BLOCKS): 

2716 return True 

2717 return False 

2718 

2719 def updateBlockInfo(self): 

2720 numUpdated = 0 

2721 for vdi in self.vdis.values(): 

2722 if vdi.scanError or vdi.raw or len(vdi.children) == 0: 

2723 continue 

2724 if not vdi.getConfig(vdi.DB_VHD_BLOCKS): 

2725 vdi.updateBlockInfo() 

2726 numUpdated += 1 

2727 if numUpdated: 

2728 # deactivate the LVs back sooner rather than later. If we don't 

2729 # now, by the time this thread gets to deactivations, another one 

2730 # might have leaf-coalesced a node and deleted it, making the child 

2731 # inherit the refcount value and preventing the correct decrement 

2732 self.cleanup() 

2733 

2734 def scan(self, force=False): 

2735 vdis = self._scan(force) 

2736 for uuid, vdiInfo in vdis.items(): 

2737 vdi = self.getVDI(uuid) 

2738 if not vdi: 

2739 self.logFilter.logNewVDI(uuid) 

2740 vdi = LVHDVDI(self, uuid, 

2741 vdiInfo.vdiType == vhdutil.VDI_TYPE_RAW) 

2742 self.vdis[uuid] = vdi 

2743 vdi.load(vdiInfo) 

2744 self._removeStaleVDIs(vdis.keys()) 

2745 self._buildTree(force) 

2746 self.logFilter.logState() 

2747 self._handleInterruptedCoalesceLeaf() 

2748 

2749 def _scan(self, force): 

2750 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2751 error = False 

2752 self.lvmCache.refresh() 

2753 vdis = lvhdutil.getVDIInfo(self.lvmCache) 

2754 for uuid, vdiInfo in vdis.items(): 

2755 if vdiInfo.scanError: 

2756 error = True 

2757 break 

2758 if not error: 

2759 return vdis 

2760 Util.log("Scan error, retrying (%d)" % i) 

2761 if force: 

2762 return vdis 

2763 raise util.SMException("Scan error") 

2764 

2765 def _removeStaleVDIs(self, uuidsPresent): 

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

2767 if not uuid in uuidsPresent: 

2768 Util.log("VDI %s disappeared since last scan" % \ 

2769 self.vdis[uuid]) 

2770 del self.vdis[uuid] 

2771 if self.lvActivator.get(uuid, False): 

2772 self.lvActivator.remove(uuid, False) 

2773 

2774 def _liveLeafCoalesce(self, vdi): 

2775 """If the parent is raw and the child was resized (virt. size), then 

2776 we'll need to resize the parent, which can take a while due to zeroing 

2777 out of the extended portion of the LV. Do it before pausing the child 

2778 to avoid a protracted downtime""" 

2779 if vdi.parent.raw and vdi.sizeVirt > vdi.parent.sizeVirt: 

2780 self.lvmCache.setReadonly(vdi.parent.fileName, False) 

2781 vdi.parent._increaseSizeVirt(vdi.sizeVirt) 

2782 

2783 return SR._liveLeafCoalesce(self, vdi) 

2784 

2785 def _prepareCoalesceLeaf(self, vdi): 

2786 vdi._activateChain() 

2787 self.lvmCache.setReadonly(vdi.parent.fileName, False) 

2788 vdi.deflate() 

2789 vdi.inflateParentForCoalesce() 

2790 

2791 def _updateNode(self, vdi): 

2792 # fix the refcounts: the remaining node should inherit the binary 

2793 # refcount from the leaf (because if it was online, it should remain 

2794 # refcounted as such), but the normal refcount from the parent (because 

2795 # this node is really the parent node) - minus 1 if it is online (since 

2796 # non-leaf nodes increment their normal counts when they are online and 

2797 # we are now a leaf, storing that 1 in the binary refcount). 

2798 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

2799 cCnt, cBcnt = RefCounter.check(vdi.uuid, ns) 

2800 pCnt, pBcnt = RefCounter.check(vdi.parent.uuid, ns) 

2801 pCnt = pCnt - cBcnt 

2802 assert(pCnt >= 0) 

2803 RefCounter.set(vdi.parent.uuid, pCnt, cBcnt, ns) 

2804 

2805 def _finishCoalesceLeaf(self, parent): 

2806 if not parent.isSnapshot() or parent.isAttachedRW(): 

2807 parent.inflateFully() 

2808 else: 

2809 parent.deflate() 

2810 

2811 def _calcExtraSpaceNeeded(self, child, parent): 

2812 return lvhdutil.calcSizeVHDLV(parent.sizeVirt) - parent.sizeLV 

2813 

2814 def _handleInterruptedCoalesceLeaf(self): 

2815 entries = self.journaler.getAll(VDI.JRN_LEAF) 

2816 for uuid, parentUuid in entries.items(): 

2817 childLV = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_VHD] + uuid 

2818 tmpChildLV = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_VHD] + \ 

2819 self.TMP_RENAME_PREFIX + uuid 

2820 parentLV1 = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_VHD] + parentUuid 

2821 parentLV2 = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_RAW] + parentUuid 

2822 parentPresent = (self.lvmCache.checkLV(parentLV1) or \ 

2823 self.lvmCache.checkLV(parentLV2)) 

2824 if parentPresent or self.lvmCache.checkLV(tmpChildLV): 

2825 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

2826 else: 

2827 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

2828 self.journaler.remove(VDI.JRN_LEAF, uuid) 

2829 vdi = self.getVDI(uuid) 

2830 if vdi: 

2831 vdi.ensureUnpaused() 

2832 

2833 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

2834 Util.log("*** UNDO LEAF-COALESCE") 

2835 parent = self.getVDI(parentUuid) 

2836 if not parent: 

2837 parent = self.getVDI(childUuid) 

2838 if not parent: 

2839 raise util.SMException("Neither %s nor %s found" % \ 

2840 (parentUuid, childUuid)) 

2841 Util.log("Renaming parent back: %s -> %s" % (childUuid, parentUuid)) 

2842 parent.rename(parentUuid) 

2843 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_rename", self.uuid) 

2844 

2845 child = self.getVDI(childUuid) 

2846 if not child: 

2847 child = self.getVDI(self.TMP_RENAME_PREFIX + childUuid) 

2848 if not child: 

2849 raise util.SMException("Neither %s nor %s found" % \ 

2850 (childUuid, self.TMP_RENAME_PREFIX + childUuid)) 

2851 Util.log("Renaming child back to %s" % childUuid) 

2852 child.rename(childUuid) 

2853 Util.log("Updating the VDI record") 

2854 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

2855 child.setConfig(VDI.DB_VDI_TYPE, vhdutil.VDI_TYPE_VHD) 

2856 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_rename2", self.uuid) 

2857 

2858 # refcount (best effort - assume that it had succeeded if the 

2859 # second rename succeeded; if not, this adjustment will be wrong, 

2860 # leading to a non-deactivation of the LV) 

2861 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

2862 cCnt, cBcnt = RefCounter.check(child.uuid, ns) 

2863 pCnt, pBcnt = RefCounter.check(parent.uuid, ns) 

2864 pCnt = pCnt + cBcnt 

2865 RefCounter.set(parent.uuid, pCnt, 0, ns) 

2866 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_refcount", self.uuid) 

2867 

2868 parent.deflate() 

2869 child.inflateFully() 

2870 util.fistpoint.activate("LVHDRT_coaleaf_undo_after_deflate", self.uuid) 

2871 if child.isHidden(): 

2872 child._setHidden(False) 

2873 if not parent.isHidden(): 

2874 parent._setHidden(True) 

2875 if not parent.lvReadonly: 

2876 self.lvmCache.setReadonly(parent.fileName, True) 

2877 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

2878 util.fistpoint.activate("LVHDRT_coaleaf_undo_end", self.uuid) 

2879 Util.log("*** leaf-coalesce undo successful") 

2880 if util.fistpoint.is_active("LVHDRT_coaleaf_stop_after_recovery"): 

2881 child.setConfig(VDI.DB_LEAFCLSC, VDI.LEAFCLSC_DISABLED) 

2882 

2883 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

2884 Util.log("*** FINISH LEAF-COALESCE") 

2885 vdi = self.getVDI(childUuid) 

2886 if not vdi: 

2887 raise util.SMException("VDI %s not found" % childUuid) 

2888 vdi.inflateFully() 

2889 util.fistpoint.activate("LVHDRT_coaleaf_finish_after_inflate", self.uuid) 

2890 try: 

2891 self.forgetVDI(parentUuid) 

2892 except XenAPI.Failure: 

2893 pass 

2894 self._updateSlavesOnResize(vdi) 

2895 util.fistpoint.activate("LVHDRT_coaleaf_finish_end", self.uuid) 

2896 Util.log("*** finished leaf-coalesce successfully") 

2897 

2898 def _checkSlaves(self, vdi): 

2899 """Confirm with all slaves in the pool that 'vdi' is not in use. We 

2900 try to check all slaves, including those that the Agent believes are 

2901 offline, but ignore failures for offline hosts. This is to avoid cases 

2902 where the Agent thinks a host is offline but the host is up.""" 

2903 args = {"vgName": self.vgName, 

2904 "action1": "deactivateNoRefcount", 

2905 "lvName1": vdi.fileName, 

2906 "action2": "cleanupLockAndRefcount", 

2907 "uuid2": vdi.uuid, 

2908 "ns2": lvhdutil.NS_PREFIX_LVM + self.uuid} 

2909 onlineHosts = self.xapi.getOnlineHosts() 

2910 abortFlag = IPCFlag(self.uuid) 

2911 for pbdRecord in self.xapi.getAttachedPBDs(): 

2912 hostRef = pbdRecord["host"] 

2913 if hostRef == self.xapi._hostRef: 

2914 continue 

2915 if abortFlag.test(FLAG_TYPE_ABORT): 

2916 raise AbortException("Aborting due to signal") 

2917 Util.log("Checking with slave %s (path %s)" % ( 

2918 self.xapi.getRecordHost(hostRef)['hostname'], vdi.path)) 

2919 try: 

2920 self.xapi.ensureInactive(hostRef, args) 

2921 except XenAPI.Failure: 

2922 if hostRef in onlineHosts: 

2923 raise 

2924 

2925 def _updateSlavesOnUndoLeafCoalesce(self, parent, child): 

2926 slaves = util.get_slaves_attached_on(self.xapi.session, [child.uuid]) 

2927 if not slaves: 

2928 Util.log("Update-on-leaf-undo: VDI %s not attached on any slave" % \ 

2929 child) 

2930 return 

2931 

2932 tmpName = lvhdutil.LV_PREFIX[vhdutil.VDI_TYPE_VHD] + \ 

2933 self.TMP_RENAME_PREFIX + child.uuid 

2934 args = {"vgName": self.vgName, 

2935 "action1": "deactivateNoRefcount", 

2936 "lvName1": tmpName, 

2937 "action2": "deactivateNoRefcount", 

2938 "lvName2": child.fileName, 

2939 "action3": "refresh", 

2940 "lvName3": child.fileName, 

2941 "action4": "refresh", 

2942 "lvName4": parent.fileName} 

2943 for slave in slaves: 

2944 Util.log("Updating %s, %s, %s on slave %s" % \ 

2945 (tmpName, child.fileName, parent.fileName, 

2946 self.xapi.getRecordHost(slave)['hostname'])) 

2947 text = self.xapi.session.xenapi.host.call_plugin( \ 

2948 slave, self.xapi.PLUGIN_ON_SLAVE, "multi", args) 

2949 Util.log("call-plugin returned: '%s'" % text) 

2950 

2951 def _updateSlavesOnRename(self, vdi, oldNameLV, origParentUuid): 

2952 slaves = util.get_slaves_attached_on(self.xapi.session, [vdi.uuid]) 

2953 if not slaves: 

2954 Util.log("Update-on-rename: VDI %s not attached on any slave" % vdi) 

2955 return 

2956 

2957 args = {"vgName": self.vgName, 

2958 "action1": "deactivateNoRefcount", 

2959 "lvName1": oldNameLV, 

2960 "action2": "refresh", 

2961 "lvName2": vdi.fileName, 

2962 "action3": "cleanupLockAndRefcount", 

2963 "uuid3": origParentUuid, 

2964 "ns3": lvhdutil.NS_PREFIX_LVM + self.uuid} 

2965 for slave in slaves: 

2966 Util.log("Updating %s to %s on slave %s" % \ 

2967 (oldNameLV, vdi.fileName, 

2968 self.xapi.getRecordHost(slave)['hostname'])) 

2969 text = self.xapi.session.xenapi.host.call_plugin( \ 

2970 slave, self.xapi.PLUGIN_ON_SLAVE, "multi", args) 

2971 Util.log("call-plugin returned: '%s'" % text) 

2972 

2973 def _updateSlavesOnResize(self, vdi): 

2974 uuids = [x.uuid for x in vdi.getAllLeaves()] 

2975 slaves = util.get_slaves_attached_on(self.xapi.session, uuids) 

2976 if not slaves: 

2977 util.SMlog("Update-on-resize: %s not attached on any slave" % vdi) 

2978 return 

2979 lvhdutil.lvRefreshOnSlaves(self.xapi.session, self.uuid, self.vgName, 

2980 vdi.fileName, vdi.uuid, slaves) 

2981 

2982 

2983################################################################################ 

2984# 

2985# Helpers 

2986# 

2987def daemonize(): 

2988 pid = os.fork() 

2989 if pid: 

2990 os.waitpid(pid, 0) 

2991 Util.log("New PID [%d]" % pid) 

2992 return False 

2993 os.chdir("/") 

2994 os.setsid() 

2995 pid = os.fork() 

2996 if pid: 

2997 Util.log("Will finish as PID [%d]" % pid) 

2998 os._exit(0) 

2999 for fd in [0, 1, 2]: 

3000 try: 

3001 os.close(fd) 

3002 except OSError: 

3003 pass 

3004 # we need to fill those special fd numbers or pread won't work 

3005 sys.stdin = open("/dev/null", 'r') 

3006 sys.stderr = open("/dev/null", 'w') 

3007 sys.stdout = open("/dev/null", 'w') 

3008 # As we're a new process we need to clear the lock objects 

3009 lock.Lock.clearAll() 

3010 return True 

3011 

3012 

3013def normalizeType(type): 

3014 if type in LVHDSR.SUBTYPES: 

3015 type = SR.TYPE_LVHD 

3016 if type in ["lvm", "lvmoiscsi", "lvmohba"]: 

3017 # temporary while LVHD is symlinked as LVM 

3018 type = SR.TYPE_LVHD 

3019 if type in ["ext", "nfs", "ocfsoiscsi", "ocfsohba", "smb"]: 

3020 type = SR.TYPE_FILE 

3021 if not type in SR.TYPES: 

3022 raise util.SMException("Unsupported SR type: %s" % type) 

3023 return type 

3024 

3025GCPAUSE_DEFAULT_SLEEP = 5 * 60 

3026 

3027 

3028def _gc_init_file(sr_uuid): 

3029 return os.path.join(NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init') 

3030 

3031 

3032def _create_init_file(sr_uuid): 

3033 util.makedirs(os.path.join(NON_PERSISTENT_DIR, str(sr_uuid))) 

3034 with open(os.path.join( 

3035 NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init'), 'w+') as f: 

3036 f.write('1') 

3037 

3038 

3039def _gcLoopPause(sr, dryRun=False, immediate=False): 

3040 if immediate: 

3041 return 

3042 

3043 # Check to see if the GCPAUSE_FISTPOINT is present. If so the fist 

3044 # point will just return. Otherwise, fall back on an abortable sleep. 

3045 

3046 if util.fistpoint.is_active(util.GCPAUSE_FISTPOINT): 

3047 

3048 util.fistpoint.activate_custom_fn(util.GCPAUSE_FISTPOINT, 3048 ↛ exitline 3048 didn't jump to the function exit

3049 lambda *args: None) 

3050 elif os.path.exists(_gc_init_file(sr.uuid)): 

3051 def abortTest(): 

3052 return IPCFlag(sr.uuid).test(FLAG_TYPE_ABORT) 

3053 

3054 # If time.sleep hangs we are in deep trouble, however for 

3055 # completeness we set the timeout of the abort thread to 

3056 # 110% of GCPAUSE_DEFAULT_SLEEP. 

3057 Util.log("GC active, about to go quiet") 

3058 Util.runAbortable(lambda: time.sleep(GCPAUSE_DEFAULT_SLEEP), 3058 ↛ exitline 3058 didn't run the lambda on line 3058

3059 None, sr.uuid, abortTest, VDI.POLL_INTERVAL, 

3060 GCPAUSE_DEFAULT_SLEEP * 1.1) 

3061 Util.log("GC active, quiet period ended") 

3062 

3063 

3064def _gcLoop(sr, dryRun=False, immediate=False): 

3065 if not lockGCActive.acquireNoblock(): 3065 ↛ 3066line 3065 didn't jump to line 3066, because the condition on line 3065 was never true

3066 Util.log("Another GC instance already active, exiting") 

3067 return 

3068 

3069 # Check we're still attached after acquiring locks 

3070 if not sr.xapi.isPluggedHere(): 

3071 Util.log("SR no longer attached, exiting") 

3072 return 

3073 

3074 # Clean up Intellicache files 

3075 sr.cleanupCache() 

3076 

3077 # Track how many we do 

3078 coalesced = 0 

3079 task_status = "success" 

3080 try: 

3081 # Check if any work needs to be done 

3082 if not sr.xapi.isPluggedHere(): 3082 ↛ 3083line 3082 didn't jump to line 3083, because the condition on line 3082 was never true

3083 Util.log("SR no longer attached, exiting") 

3084 return 

3085 sr.scanLocked() 

3086 if not sr.hasWork(): 

3087 Util.log("No work, exiting") 

3088 return 

3089 sr.xapi.create_task( 

3090 "Garbage Collection", 

3091 "Garbage collection for SR %s" % sr.uuid) 

3092 _gcLoopPause(sr, dryRun, immediate=immediate) 

3093 while True: 

3094 if SIGTERM: 

3095 Util.log("Term requested") 

3096 return 

3097 

3098 if not sr.xapi.isPluggedHere(): 3098 ↛ 3099line 3098 didn't jump to line 3099, because the condition on line 3098 was never true

3099 Util.log("SR no longer attached, exiting") 

3100 break 

3101 sr.scanLocked() 

3102 if not sr.hasWork(): 

3103 Util.log("No work, exiting") 

3104 break 

3105 

3106 if not lockGCRunning.acquireNoblock(): 3106 ↛ 3107line 3106 didn't jump to line 3107, because the condition on line 3106 was never true

3107 Util.log("Unable to acquire GC running lock.") 

3108 return 

3109 try: 

3110 if not sr.gcEnabled(): 3110 ↛ 3111line 3110 didn't jump to line 3111, because the condition on line 3110 was never true

3111 break 

3112 

3113 sr.xapi.update_task_progress("done", coalesced) 

3114 

3115 sr.cleanupCoalesceJournals() 

3116 # Create the init file here in case startup is waiting on it 

3117 _create_init_file(sr.uuid) 

3118 sr.scanLocked() 

3119 sr.updateBlockInfo() 

3120 

3121 howmany = len(sr.findGarbage()) 

3122 if howmany > 0: 

3123 Util.log("Found %d orphaned vdis" % howmany) 

3124 sr.lock() 

3125 try: 

3126 sr.garbageCollect(dryRun) 

3127 finally: 

3128 sr.unlock() 

3129 sr.xapi.srUpdate() 

3130 

3131 candidate = sr.findCoalesceable() 

3132 if candidate: 

3133 util.fistpoint.activate( 

3134 "LVHDRT_finding_a_suitable_pair", sr.uuid) 

3135 sr.coalesce(candidate, dryRun) 

3136 sr.xapi.srUpdate() 

3137 coalesced += 1 

3138 continue 

3139 

3140 candidate = sr.findLeafCoalesceable() 

3141 if candidate: 3141 ↛ 3148line 3141 didn't jump to line 3148, because the condition on line 3141 was never false

3142 sr.coalesceLeaf(candidate, dryRun) 

3143 sr.xapi.srUpdate() 

3144 coalesced += 1 

3145 continue 

3146 

3147 finally: 

3148 lockGCRunning.release() 3148 ↛ 3153line 3148 didn't jump to line 3153, because the break on line 3111 wasn't executed

3149 except: 

3150 task_status = "failure" 

3151 raise 

3152 finally: 

3153 sr.xapi.set_task_status(task_status) 

3154 Util.log("GC process exiting, no work left") 

3155 _create_init_file(sr.uuid) 

3156 lockGCActive.release() 

3157 

3158 

3159def _gc(session, srUuid, dryRun=False, immediate=False): 

3160 init(srUuid) 

3161 sr = SR.getInstance(srUuid, session) 

3162 if not sr.gcEnabled(False): 3162 ↛ 3163line 3162 didn't jump to line 3163, because the condition on line 3162 was never true

3163 return 

3164 

3165 try: 

3166 _gcLoop(sr, dryRun, immediate=immediate) 

3167 finally: 

3168 sr.check_no_space_candidates() 

3169 sr.cleanup() 

3170 sr.logFilter.logState() 

3171 del sr.xapi 

3172 

3173 

3174def _abort(srUuid, soft=False): 

3175 """Aborts an GC/coalesce. 

3176 

3177 srUuid: the UUID of the SR whose GC/coalesce must be aborted 

3178 soft: If set to True and there is a pending abort signal, the function 

3179 doesn't do anything. If set to False, a new abort signal is issued. 

3180 

3181 returns: If soft is set to False, we return True holding lockGCActive. If 

3182 soft is set to False and an abort signal is pending, we return False 

3183 without holding lockGCActive. An exception is raised in case of error.""" 

3184 Util.log("=== SR %s: abort ===" % (srUuid)) 

3185 init(srUuid) 

3186 if not lockGCActive.acquireNoblock(): 

3187 gotLock = False 

3188 Util.log("Aborting currently-running instance (SR %s)" % srUuid) 

3189 abortFlag = IPCFlag(srUuid) 

3190 if not abortFlag.set(FLAG_TYPE_ABORT, soft): 

3191 return False 

3192 for i in range(SR.LOCK_RETRY_ATTEMPTS): 

3193 gotLock = lockGCActive.acquireNoblock() 

3194 if gotLock: 

3195 break 

3196 time.sleep(SR.LOCK_RETRY_INTERVAL) 

3197 abortFlag.clear(FLAG_TYPE_ABORT) 

3198 if not gotLock: 

3199 raise util.CommandException(code=errno.ETIMEDOUT, 

3200 reason="SR %s: error aborting existing process" % srUuid) 

3201 return True 

3202 

3203 

3204def init(srUuid): 

3205 global lockGCRunning 

3206 if not lockGCRunning: 3206 ↛ 3207line 3206 didn't jump to line 3207, because the condition on line 3206 was never true

3207 lockGCRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, srUuid) 

3208 global lockGCActive 

3209 if not lockGCActive: 3209 ↛ 3210line 3209 didn't jump to line 3210, because the condition on line 3209 was never true

3210 lockGCActive = LockActive(srUuid) 

3211 

3212 

3213class LockActive: 

3214 """ 

3215 Wraps the use of LOCK_TYPE_GC_ACTIVE such that the lock cannot be acquired 

3216 if another process holds the SR lock. 

3217 """ 

3218 def __init__(self, srUuid): 

3219 self._lock = lock.Lock(LOCK_TYPE_GC_ACTIVE, srUuid) 

3220 self._srLock = lock.Lock(vhdutil.LOCK_TYPE_SR, srUuid) 

3221 

3222 def acquireNoblock(self): 

3223 self._srLock.acquire() 

3224 

3225 try: 

3226 return self._lock.acquireNoblock() 

3227 finally: 

3228 self._srLock.release() 

3229 

3230 def release(self): 

3231 self._lock.release() 

3232 

3233 

3234############################################################################## 

3235# 

3236# API 

3237# 

3238def abort(srUuid, soft=False): 

3239 """Abort GC/coalesce if we are currently GC'ing or coalescing a VDI pair. 

3240 """ 

3241 if _abort(srUuid, soft): 

3242 stop_gc_service(srUuid) 

3243 Util.log("abort: releasing the process lock") 

3244 lockGCActive.release() 

3245 return True 

3246 else: 

3247 return False 

3248 

3249 

3250def run_gc(session, srUuid, dryRun, immediate=False): 

3251 try: 

3252 _gc(session, srUuid, dryRun, immediate=immediate) 

3253 return 0 

3254 except AbortException: 

3255 Util.log("Aborted") 

3256 return 2 

3257 except Exception: 

3258 Util.logException("gc") 

3259 Util.log("* * * * * SR %s: ERROR\n" % srUuid) 

3260 return 1 

3261 

3262 

3263def gc(session, srUuid, inBackground, dryRun=False): 

3264 """Garbage collect all deleted VDIs in SR "srUuid". Fork & return 

3265 immediately if inBackground=True. 

3266 

3267 The following algorithm is used: 

3268 1. If we are already GC'ing in this SR, return 

3269 2. If we are already coalescing a VDI pair: 

3270 a. Scan the SR and determine if the VDI pair is GC'able 

3271 b. If the pair is not GC'able, return 

3272 c. If the pair is GC'able, abort coalesce 

3273 3. Scan the SR 

3274 4. If there is nothing to collect, nor to coalesce, return 

3275 5. If there is something to collect, GC all, then goto 3 

3276 6. If there is something to coalesce, coalesce one pair, then goto 3 

3277 """ 

3278 Util.log("=== SR %s: gc ===" % srUuid) 

3279 

3280 signal.signal(signal.SIGTERM, receiveSignal) 

3281 

3282 if inBackground: 

3283 if daemonize(): 3283 ↛ exitline 3283 didn't return from function 'gc', because the condition on line 3283 was never false

3284 # we are now running in the background. Catch & log any errors 

3285 # because there is no other way to propagate them back at this 

3286 # point 

3287 

3288 run_gc(None, srUuid, dryRun) 

3289 os._exit(0) 

3290 else: 

3291 os._exit(run_gc(session, srUuid, dryRun, immediate=True)) 

3292 

3293 

3294def start_gc(session, sr_uuid): 

3295 """ 

3296 This function is used to try to start a backgrounded GC session by forking 

3297 the current process. If using the systemd version, call start_gc_service() instead. 

3298 """ 

3299 # don't bother if an instance already running (this is just an 

3300 # optimization to reduce the overhead of forking a new process if we 

3301 # don't have to, but the process will check the lock anyways) 

3302 lockRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, sr_uuid) 

3303 if not lockRunning.acquireNoblock(): 

3304 if should_preempt(session, sr_uuid): 

3305 util.SMlog("Aborting currently-running coalesce of garbage VDI") 

3306 try: 

3307 if not abort(sr_uuid, soft=True): 

3308 util.SMlog("The GC has already been scheduled to re-start") 

3309 except util.CommandException as e: 

3310 if e.code != errno.ETIMEDOUT: 

3311 raise 

3312 util.SMlog('failed to abort the GC') 

3313 else: 

3314 util.SMlog("A GC instance already running, not kicking") 

3315 return 

3316 else: 

3317 lockRunning.release() 

3318 

3319 util.SMlog(f"Starting GC file is {__file__}") 

3320 subprocess.run([__file__, '-b', '-u', sr_uuid, '-g'], 

3321 stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3322 

3323def _gc_service_cmd(sr_uuid, action, extra_args=None): 

3324 """ 

3325 Build and run the systemctl command for the GC service using util.doexec. 

3326 """ 

3327 sr_uuid_esc = sr_uuid.replace("-", "\\x2d") 

3328 cmd=["/usr/bin/systemctl", "--quiet"] 

3329 if extra_args: 3329 ↛ 3331line 3329 didn't jump to line 3331, because the condition on line 3329 was never false

3330 cmd.extend(extra_args) 

3331 cmd += [action, f"SMGC@{sr_uuid_esc}"] 

3332 return util.doexec(cmd) 

3333 

3334 

3335def start_gc_service(sr_uuid, wait=False): 

3336 """ 

3337 This starts the templated systemd service which runs GC on the given SR UUID. 

3338 If the service was already started, this is a no-op. 

3339 

3340 Because the service is a one-shot with RemainAfterExit=no, when called with 

3341 wait=True this will run the service synchronously and will not return until the 

3342 run has finished. This is used to force a run of the GC instead of just kicking it 

3343 in the background. 

3344 """ 

3345 util.SMlog(f"Kicking SMGC@{sr_uuid}...") 

3346 _gc_service_cmd(sr_uuid, "start", extra_args=None if wait else ["--no-block"]) 

3347 

3348 

3349def stop_gc_service(sr_uuid): 

3350 """ 

3351 Stops the templated systemd service which runs GC on the given SR UUID. 

3352 """ 

3353 util.SMlog(f"Stopping SMGC@{sr_uuid}...") 

3354 (rc, _stdout, stderr) = _gc_service_cmd(sr_uuid, "stop") 

3355 if rc != 0: 

3356 util.SMlog(f"Failed to stop gc service `SMGC@{sr_uuid}`: `{stderr}`") 

3357 

3358 

3359def wait_for_completion(sr_uuid): 

3360 while get_state(sr_uuid): 

3361 time.sleep(5) 

3362 

3363 

3364def gc_force(session, srUuid, force=False, dryRun=False, lockSR=False): 

3365 """Garbage collect all deleted VDIs in SR "srUuid". The caller must ensure 

3366 the SR lock is held. 

3367 The following algorithm is used: 

3368 1. If we are already GC'ing or coalescing a VDI pair, abort GC/coalesce 

3369 2. Scan the SR 

3370 3. GC 

3371 4. return 

3372 """ 

3373 Util.log("=== SR %s: gc_force ===" % srUuid) 

3374 init(srUuid) 

3375 sr = SR.getInstance(srUuid, session, lockSR, True) 

3376 if not lockGCActive.acquireNoblock(): 

3377 abort(srUuid) 

3378 else: 

3379 Util.log("Nothing was running, clear to proceed") 

3380 

3381 if force: 

3382 Util.log("FORCED: will continue even if there are VHD errors") 

3383 sr.scanLocked(force) 

3384 sr.cleanupCoalesceJournals() 

3385 

3386 try: 

3387 sr.cleanupCache() 

3388 sr.garbageCollect(dryRun) 

3389 finally: 

3390 sr.cleanup() 

3391 sr.logFilter.logState() 

3392 lockGCActive.release() 

3393 

3394 

3395def get_state(srUuid): 

3396 """Return whether GC/coalesce is currently running or not. This asks systemd for 

3397 the state of the templated SMGC service and will return True if it is "activating" 

3398 or "running" (for completeness, as in practice it will never achieve the latter state) 

3399 """ 

3400 sr_uuid_esc = srUuid.replace("-", "\\x2d") 

3401 cmd=[ "/usr/bin/systemctl", "is-active", f"SMGC@{sr_uuid_esc}"] 

3402 result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3403 state = result.stdout.decode('utf-8').rstrip() 

3404 if state == "activating" or state == "running": 

3405 return True 

3406 return False 

3407 

3408 

3409def should_preempt(session, srUuid): 

3410 sr = SR.getInstance(srUuid, session) 

3411 entries = sr.journaler.getAll(VDI.JRN_COALESCE) 

3412 if len(entries) == 0: 

3413 return False 

3414 elif len(entries) > 1: 

3415 raise util.SMException("More than one coalesce entry: " + str(entries)) 

3416 sr.scanLocked() 

3417 coalescedUuid = entries.popitem()[0] 

3418 garbage = sr.findGarbage() 

3419 for vdi in garbage: 

3420 if vdi.uuid == coalescedUuid: 

3421 return True 

3422 return False 

3423 

3424 

3425def get_coalesceable_leaves(session, srUuid, vdiUuids): 

3426 coalesceable = [] 

3427 sr = SR.getInstance(srUuid, session) 

3428 sr.scanLocked() 

3429 for uuid in vdiUuids: 

3430 vdi = sr.getVDI(uuid) 

3431 if not vdi: 

3432 raise util.SMException("VDI %s not found" % uuid) 

3433 if vdi.isLeafCoalesceable(): 

3434 coalesceable.append(uuid) 

3435 return coalesceable 

3436 

3437 

3438def cache_cleanup(session, srUuid, maxAge): 

3439 sr = SR.getInstance(srUuid, session) 

3440 return sr.cleanupCache(maxAge) 

3441 

3442 

3443def debug(sr_uuid, cmd, vdi_uuid): 

3444 Util.log("Debug command: %s" % cmd) 

3445 sr = SR.getInstance(sr_uuid, None) 

3446 if not isinstance(sr, LVHDSR): 

3447 print("Error: not an LVHD SR") 

3448 return 

3449 sr.scanLocked() 

3450 vdi = sr.getVDI(vdi_uuid) 

3451 if not vdi: 

3452 print("Error: VDI %s not found") 

3453 return 

3454 print("Running %s on SR %s" % (cmd, sr)) 

3455 print("VDI before: %s" % vdi) 

3456 if cmd == "activate": 

3457 vdi._activate() 

3458 print("VDI file: %s" % vdi.path) 

3459 if cmd == "deactivate": 

3460 ns = lvhdutil.NS_PREFIX_LVM + sr.uuid 

3461 sr.lvmCache.deactivate(ns, vdi.uuid, vdi.fileName, False) 

3462 if cmd == "inflate": 

3463 vdi.inflateFully() 

3464 sr.cleanup() 

3465 if cmd == "deflate": 

3466 vdi.deflate() 

3467 sr.cleanup() 

3468 sr.scanLocked() 

3469 print("VDI after: %s" % vdi) 

3470 

3471 

3472def abort_optional_reenable(uuid): 

3473 print("Disabling GC/coalesce for %s" % uuid) 

3474 ret = _abort(uuid) 

3475 input("Press enter to re-enable...") 

3476 print("GC/coalesce re-enabled") 

3477 lockGCRunning.release() 

3478 if ret: 

3479 lockGCActive.release()