Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/python3 

2# 

3# Copyright (C) Citrix Systems Inc. 

4# 

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

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

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

8# 

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

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

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

12# GNU Lesser General Public License for more details. 

13# 

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

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

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

17# 

18# Script to coalesce and garbage collect VHD-based SR's in the background 

19# 

20 

21from sm_typing import Optional, override 

22 

23import os 

24import os.path 

25import sys 

26import time 

27import signal 

28import subprocess 

29import getopt 

30import datetime 

31import traceback 

32import base64 

33import zlib 

34import errno 

35import stat 

36 

37import XenAPI # pylint: disable=import-error 

38import util 

39import lvutil 

40import vhdutil 

41import lvhdutil 

42import lvmcache 

43import journaler 

44import fjournaler 

45import lock 

46import blktap2 

47import xs_errors 

48from refcounter import RefCounter 

49from ipc import IPCFlag 

50from lvmanager import LVActivator 

51from srmetadata import LVMMetadataHandler, VDI_TYPE_TAG 

52from functools import reduce 

53from time import monotonic as _time 

54 

55try: 

56 from linstorjournaler import LinstorJournaler 

57 from linstorvhdutil import LinstorVhdUtil 

58 from linstorvolumemanager import get_controller_uri 

59 from linstorvolumemanager import LinstorVolumeManager 

60 from linstorvolumemanager import LinstorVolumeManagerError 

61 from linstorvolumemanager import PERSISTENT_PREFIX as LINSTOR_PERSISTENT_PREFIX 

62 

63 LINSTOR_AVAILABLE = True 

64except ImportError: 

65 LINSTOR_AVAILABLE = False 

66 

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

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

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

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

71# the VM first) 

72AUTO_ONLINE_LEAF_COALESCE_ENABLED = True 

73 

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

75 

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

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

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

79lockGCRunning = None 

80 

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

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

83LOCK_TYPE_GC_ACTIVE = "gc_active" 

84lockGCActive = None 

85 

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

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

88DEFAULT_COALESCE_ERR_RATE = 1.0 / 60 

89 

90COALESCE_LAST_ERR_TAG = 'last-coalesce-error' 

91COALESCE_ERR_RATE_TAG = 'coalesce-error-rate' 

92VAR_RUN = "/var/run/" 

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

94 

95N_RUNNING_AVERAGE = 10 

96 

97NON_PERSISTENT_DIR = '/run/nonpersistent/sm' 

98 

99 

100class AbortException(util.SMException): 

101 pass 

102 

103 

104################################################################################ 

105# 

106# Util 

107# 

108class Util: 

109 RET_RC = 1 

110 RET_STDOUT = 2 

111 RET_STDERR = 4 

112 

113 UUID_LEN = 36 

114 

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

116 

117 @staticmethod 

118 def log(text) -> None: 

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

120 

121 @staticmethod 

122 def logException(tag): 

123 info = sys.exc_info() 

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

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

126 sys.exit(0) 

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

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

129 Util.log(" ***********************") 

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

131 Util.log(" ***********************") 

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

133 Util.log(tb) 

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

135 

136 @staticmethod 

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

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

139 proc = subprocess.Popen(args, 

140 stdin=subprocess.PIPE, \ 

141 stdout=subprocess.PIPE, \ 

142 stderr=subprocess.PIPE, \ 

143 shell=True, \ 

144 close_fds=True) 

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

146 stdout = str(stdout) 

147 stderr = str(stderr) 

148 rc = proc.returncode 

149 if log: 

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

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

152 expectedRC = [expectedRC] 

153 if not rc in expectedRC: 

154 reason = stderr.strip() 

155 if stdout.strip(): 

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

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

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

159 

160 if ret == Util.RET_RC: 

161 return rc 

162 if ret == Util.RET_STDERR: 

163 return stderr 

164 return stdout 

165 

166 @staticmethod 

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

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

169 so""" 

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

171 resultFlag = IPCFlag(ns) 

172 resultFlag.clearAll() 

173 pid = os.fork() 

174 if pid: 

175 startTime = _time() 

176 try: 

177 while True: 

178 if resultFlag.test("success"): 

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

180 resultFlag.clear("success") 

181 return 

182 if resultFlag.test("failure"): 

183 resultFlag.clear("failure") 

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

185 if abortTest() or abortSignaled: 

186 os.killpg(pid, signal.SIGKILL) 

187 raise AbortException("Aborting due to signal") 

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

189 os.killpg(pid, signal.SIGKILL) 

190 resultFlag.clearAll() 

191 raise util.SMException("Timed out") 

192 time.sleep(pollInterval) 

193 finally: 

194 wait_pid = 0 

195 rc = -1 

196 count = 0 

197 while wait_pid == 0 and count < 10: 

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

199 if wait_pid == 0: 

200 time.sleep(2) 

201 count += 1 

202 

203 if wait_pid == 0: 

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

205 else: 

206 os.setpgrp() 

207 try: 

208 if func() == ret: 

209 resultFlag.set("success") 

210 else: 

211 resultFlag.set("failure") 

212 except Exception as e: 

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

214 resultFlag.set("failure") 

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

216 os._exit(0) 

217 

218 @staticmethod 

219 def num2str(number): 

220 for prefix in ("G", "M", "K"): 

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

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

223 return "%s" % number 

224 

225 @staticmethod 

226 def numBits(val): 

227 count = 0 

228 while val: 

229 count += val & 1 

230 val = val >> 1 

231 return count 

232 

233 @staticmethod 

234 def countBits(bitmap1, bitmap2): 

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

236 len1 = len(bitmap1) 

237 len2 = len(bitmap2) 

238 lenLong = len1 

239 lenShort = len2 

240 bitmapLong = bitmap1 

241 if len2 > len1: 

242 lenLong = len2 

243 lenShort = len1 

244 bitmapLong = bitmap2 

245 

246 count = 0 

247 for i in range(lenShort): 

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

249 count += Util.numBits(val) 

250 

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

252 val = bitmapLong[i] 

253 count += Util.numBits(val) 

254 return count 

255 

256 @staticmethod 

257 def getThisScript(): 

258 thisScript = util.get_real_path(__file__) 

259 if thisScript.endswith(".pyc"): 

260 thisScript = thisScript[:-1] 

261 return thisScript 

262 

263 

264################################################################################ 

265# 

266# XAPI 

267# 

268class XAPI: 

269 USER = "root" 

270 PLUGIN_ON_SLAVE = "on-slave" 

271 

272 CONFIG_SM = 0 

273 CONFIG_OTHER = 1 

274 CONFIG_ON_BOOT = 2 

275 CONFIG_ALLOW_CACHING = 3 

276 

277 CONFIG_NAME = { 

278 CONFIG_SM: "sm-config", 

279 CONFIG_OTHER: "other-config", 

280 CONFIG_ON_BOOT: "on-boot", 

281 CONFIG_ALLOW_CACHING: "allow_caching" 

282 } 

283 

284 class LookupError(util.SMException): 

285 pass 

286 

287 @staticmethod 

288 def getSession(): 

289 session = XenAPI.xapi_local() 

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

291 return session 

292 

293 def __init__(self, session, srUuid): 

294 self.sessionPrivate = False 

295 self.session = session 

296 if self.session is None: 

297 self.session = self.getSession() 

298 self.sessionPrivate = True 

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

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

301 self.hostUuid = util.get_this_host() 

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

303 self.task = None 

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

305 

306 def __del__(self): 

307 if self.sessionPrivate: 

308 self.session.xenapi.session.logout() 

309 

310 def isPluggedHere(self): 

311 pbds = self.getAttachedPBDs() 

312 for pbdRec in pbds: 

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

314 return True 

315 return False 

316 

317 def poolOK(self): 

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

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

320 if not host_rec["enabled"]: 

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

322 return False 

323 return True 

324 

325 def isMaster(self): 

326 if self.srRecord["shared"]: 

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

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

329 else: 

330 pbds = self.getAttachedPBDs() 

331 if len(pbds) < 1: 

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

333 elif len(pbds) > 1: 

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

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

336 

337 def getAttachedPBDs(self): 

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

339 attached""" 

340 attachedPBDs = [] 

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

342 for pbdRec in pbds.values(): 

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

344 attachedPBDs.append(pbdRec) 

345 return attachedPBDs 

346 

347 def getOnlineHosts(self): 

348 return util.get_online_hosts(self.session) 

349 

350 def ensureInactive(self, hostRef, args): 

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

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

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

354 

355 def getRecordHost(self, hostRef): 

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

357 

358 def _getRefVDI(self, uuid): 

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

360 

361 def getRefVDI(self, vdi): 

362 return self._getRefVDI(vdi.uuid) 

363 

364 def getRecordVDI(self, uuid): 

365 try: 

366 ref = self._getRefVDI(uuid) 

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

368 except XenAPI.Failure: 

369 return None 

370 

371 def singleSnapshotVDI(self, vdi): 

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

373 {"type": "internal"}) 

374 

375 def forgetVDI(self, srUuid, vdiUuid): 

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

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

378 try: 

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

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

381 except XenAPI.Failure: 

382 pass 

383 

384 def getConfigVDI(self, vdi, key): 

385 kind = vdi.CONFIG_TYPE[key] 

386 if kind == self.CONFIG_SM: 

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

388 elif kind == self.CONFIG_OTHER: 

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

390 elif kind == self.CONFIG_ON_BOOT: 

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

392 elif kind == self.CONFIG_ALLOW_CACHING: 

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

394 else: 

395 assert(False) 

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

397 return cfg 

398 

399 def removeFromConfigVDI(self, vdi, key): 

400 kind = vdi.CONFIG_TYPE[key] 

401 if kind == self.CONFIG_SM: 

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

403 elif kind == self.CONFIG_OTHER: 

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

405 else: 

406 assert(False) 

407 

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

409 kind = vdi.CONFIG_TYPE[key] 

410 if kind == self.CONFIG_SM: 

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

412 elif kind == self.CONFIG_OTHER: 

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

414 else: 

415 assert(False) 

416 

417 def isSnapshot(self, vdi): 

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

419 

420 def markCacheSRsDirty(self): 

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

422 'field "local_cache_enabled" = "true"') 

423 for sr_ref in sr_refs: 

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

425 util.set_dirty(self.session, sr_ref) 

426 

427 def srUpdate(self): 

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

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

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

431 cancelTask = True 

432 try: 

433 for i in range(60): 

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

435 if not status == "pending": 

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

437 cancelTask = False 

438 return 

439 if abortFlag.test(FLAG_TYPE_ABORT): 

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

441 try: 

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

443 cancelTask = False 

444 Util.log("Task cancelled") 

445 except: 

446 pass 

447 return 

448 time.sleep(1) 

449 finally: 

450 if cancelTask: 

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

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

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

454 

455 def update_task(self): 

456 self.session.xenapi.task.set_other_config( 

457 self.task, 

458 { 

459 "applies_to": self._srRef 

460 }) 

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

462 if (total > 0): 

463 self.session.xenapi.task.set_progress( 

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

465 

466 def create_task(self, label, description): 

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

468 self.update_task() 

469 

470 def update_task_progress(self, key, value): 

471 self.task_progress[key] = value 

472 if self.task: 

473 self.update_task() 

474 

475 def set_task_status(self, status): 

476 if self.task: 

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

478 

479 

480################################################################################ 

481# 

482# VDI 

483# 

484class VDI(object): 

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

486 

487 POLL_INTERVAL = 1 

488 POLL_TIMEOUT = 30 

489 DEVICE_MAJOR = 202 

490 DRIVER_NAME_VHD = "vhd" 

491 

492 # config keys & values 

493 DB_VHD_PARENT = "vhd-parent" 

494 DB_VDI_TYPE = "vdi_type" 

495 DB_VHD_BLOCKS = "vhd-blocks" 

496 DB_VDI_PAUSED = "paused" 

497 DB_VDI_RELINKING = "relinking" 

498 DB_VDI_ACTIVATING = "activating" 

499 DB_GC = "gc" 

500 DB_COALESCE = "coalesce" 

501 DB_LEAFCLSC = "leaf-coalesce" # config key 

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

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

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

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

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

507 # might be used by external components. 

508 DB_ONBOOT = "on-boot" 

509 ONBOOT_RESET = "reset" 

510 DB_ALLOW_CACHING = "allow_caching" 

511 

512 CONFIG_TYPE = { 

513 DB_VHD_PARENT: XAPI.CONFIG_SM, 

514 DB_VDI_TYPE: XAPI.CONFIG_SM, 

515 DB_VHD_BLOCKS: XAPI.CONFIG_SM, 

516 DB_VDI_PAUSED: XAPI.CONFIG_SM, 

517 DB_VDI_RELINKING: XAPI.CONFIG_SM, 

518 DB_VDI_ACTIVATING: XAPI.CONFIG_SM, 

519 DB_GC: XAPI.CONFIG_OTHER, 

520 DB_COALESCE: XAPI.CONFIG_OTHER, 

521 DB_LEAFCLSC: XAPI.CONFIG_OTHER, 

522 DB_ONBOOT: XAPI.CONFIG_ON_BOOT, 

523 DB_ALLOW_CACHING: XAPI.CONFIG_ALLOW_CACHING, 

524 } 

525 

526 LIVE_LEAF_COALESCE_MAX_SIZE = 20 * 1024 * 1024 # bytes 

527 LIVE_LEAF_COALESCE_TIMEOUT = 10 # seconds 

528 TIMEOUT_SAFETY_MARGIN = 0.5 # extra margin when calculating 

529 # feasibility of leaf coalesce 

530 

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

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

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

534 

535 STR_TREE_INDENT = 4 

536 

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

538 self.sr = sr 

539 self.scanError = True 

540 self.uuid = uuid 

541 self.raw = raw 

542 self.fileName = "" 

543 self.parentUuid = "" 

544 self.sizeVirt = -1 

545 self._sizeVHD = -1 

546 self._sizeAllocated = -1 

547 self.hidden = False 

548 self.parent = None 

549 self.children = [] 

550 self._vdiRef = None 

551 self._clearRef() 

552 

553 @staticmethod 

554 def extractUuid(path): 

555 raise NotImplementedError("Implement in sub class") 

556 

557 def load(self, info=None) -> None: 

558 """Load VDI info""" 

559 pass 

560 

561 def getDriverName(self) -> str: 

562 return self.DRIVER_NAME_VHD 

563 

564 def getRef(self): 

565 if self._vdiRef is None: 

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

567 return self._vdiRef 

568 

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

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

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

572 val = config 

573 else: 

574 val = config.get(key) 

575 if val: 

576 return val 

577 return default 

578 

579 def setConfig(self, key, val): 

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

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

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

583 

584 def delConfig(self, key): 

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

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

587 

588 def ensureUnpaused(self): 

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

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

591 self.unpause() 

592 

593 def pause(self, failfast=False) -> None: 

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

595 self.uuid, failfast): 

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

597 

598 def _report_tapdisk_unpause_error(self): 

599 try: 

600 xapi = self.sr.xapi.session.xenapi 

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

602 msg_name = "failed to unpause tapdisk" 

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

604 "VMs using this tapdisk have lost access " \ 

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

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

607 except Exception as e: 

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

609 

610 def unpause(self): 

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

612 self.uuid): 

613 self._report_tapdisk_unpause_error() 

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

615 

616 def refresh(self, ignoreNonexistent=True): 

617 """Pause-unpause in one step""" 

618 self.sr.lock() 

619 try: 

620 try: 

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

622 self.sr.uuid, self.uuid): 

623 self._report_tapdisk_unpause_error() 

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

625 except XenAPI.Failure as e: 

626 if util.isInvalidVDI(e) and ignoreNonexistent: 

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

628 return 

629 raise 

630 finally: 

631 self.sr.unlock() 

632 

633 def isSnapshot(self): 

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

635 

636 def isAttachedRW(self): 

637 return util.is_attached_rw( 

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

639 

640 def getVHDBlocks(self): 

641 val = self.updateBlockInfo() 

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

643 return bitmap 

644 

645 def isCoalesceable(self): 

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

647 return not self.scanError and \ 

648 self.parent and \ 

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

650 self.hidden and \ 

651 len(self.children) > 0 

652 

653 def isLeafCoalesceable(self): 

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

655 return not self.scanError and \ 

656 self.parent and \ 

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

658 not self.hidden and \ 

659 len(self.children) == 0 

660 

661 def canLiveCoalesce(self, speed): 

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

663 isLeafCoalesceable() already""" 

664 feasibleSize = False 

665 allowedDownTime = \ 

666 self.TIMEOUT_SAFETY_MARGIN * self.LIVE_LEAF_COALESCE_TIMEOUT 

667 vhd_size = self.getAllocatedSize() 

668 if speed: 

669 feasibleSize = \ 

670 vhd_size // speed < allowedDownTime 

671 else: 

672 feasibleSize = \ 

673 vhd_size < self.LIVE_LEAF_COALESCE_MAX_SIZE 

674 

675 return (feasibleSize or 

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

677 

678 def getAllPrunable(self): 

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

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

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

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

683 # some tapdisks could still be using the file. 

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

685 return [] 

686 if not self.scanError and self.hidden: 

687 return [self] 

688 return [] 

689 

690 thisPrunable = True 

691 vdiList = [] 

692 for child in self.children: 

693 childList = child.getAllPrunable() 

694 vdiList.extend(childList) 

695 if child not in childList: 

696 thisPrunable = False 

697 

698 # We can destroy the current VDI if all childs are hidden BUT the 

699 # current VDI must be hidden too to do that! 

700 # Example in this case (after a failed live leaf coalesce): 

701 # 

702 # SMGC: [32436] SR 07ed ('linstor-nvme-sr') (2 VDIs in 1 VHD trees): 

703 # SMGC: [32436] b5458d61(1.000G/4.127M) 

704 # SMGC: [32436] *OLD_b545(1.000G/4.129M) 

705 # 

706 # OLD_b545 is hidden and must be removed, but b5458d61 not. 

707 # Normally we are not in this function when the delete action is 

708 # executed but in `_liveLeafCoalesce`. 

709 

710 if not self.scanError and not self.hidden and thisPrunable: 

711 vdiList.append(self) 

712 return vdiList 

713 

714 def getSizeVHD(self) -> int: 

715 return self._sizeVHD 

716 

717 def getAllocatedSize(self) -> int: 

718 return self._sizeAllocated 

719 

720 def getTreeRoot(self): 

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

722 root = self 

723 while root.parent: 

724 root = root.parent 

725 return root 

726 

727 def getTreeHeight(self): 

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

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

730 return 1 

731 

732 maxChildHeight = 0 

733 for child in self.children: 

734 childHeight = child.getTreeHeight() 

735 if childHeight > maxChildHeight: 

736 maxChildHeight = childHeight 

737 

738 return maxChildHeight + 1 

739 

740 def getAllLeaves(self): 

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

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

743 return [self] 

744 

745 leaves = [] 

746 for child in self.children: 

747 leaves.extend(child.getAllLeaves()) 

748 return leaves 

749 

750 def updateBlockInfo(self) -> Optional[str]: 

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

752 self.setConfig(VDI.DB_VHD_BLOCKS, val) 

753 return val 

754 

755 def rename(self, uuid) -> None: 

756 "Rename the VDI file" 

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

758 self._clearRef() 

759 oldUuid = self.uuid 

760 self.uuid = uuid 

761 self.children = [] 

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

763 del self.sr.vdis[oldUuid] 

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

765 

766 def delete(self) -> None: 

767 "Physically delete the VDI" 

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

769 lock.Lock.cleanupAll(self.uuid) 

770 self._clear() 

771 

772 def getParent(self) -> str: 

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

774 

775 def repair(self, parent) -> None: 

776 vhdutil.repair(parent) 

777 

778 @override 

779 def __str__(self) -> str: 

780 strHidden = "" 

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

782 strHidden = "*" 

783 strSizeVirt = "?" 

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

785 strSizeVirt = Util.num2str(self.sizeVirt) 

786 strSizeVHD = "?" 

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

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

789 strSizeAllocated = "?" 

790 if self._sizeAllocated >= 0: 790 ↛ 791line 790 didn't jump to line 791, because the condition on line 790 was never true

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

792 strType = "" 

793 if self.raw: 

794 strType = "[RAW]" 

795 strSizeVHD = "" 

796 

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

798 strSizeVHD, strSizeAllocated, strType) 

799 

800 def validate(self, fast=False) -> None: 

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

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

803 

804 def _clear(self): 

805 self.uuid = "" 

806 self.path = "" 

807 self.parentUuid = "" 

808 self.parent = None 

809 self._clearRef() 

810 

811 def _clearRef(self): 

812 self._vdiRef = None 

813 

814 def _doCoalesce(self) -> None: 

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

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

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

818 were coalescing""" 

819 self.validate() 

820 self.parent.validate(True) 

821 self.parent._increaseSizeVirt(self.sizeVirt) 

822 self.sr._updateSlavesOnResize(self.parent) 

823 self._coalesceVHD(0) 

824 self.parent.validate(True) 

825 #self._verifyContents(0) 

826 self.parent.updateBlockInfo() 

827 

828 def _verifyContents(self, timeOut): 

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

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

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

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

833 Util.log(" Coalesce verification succeeded") 

834 

835 def _runTapdiskDiff(self): 

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

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

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

839 Util.doexec(cmd, 0) 

840 return True 

841 

842 @staticmethod 

843 def _reportCoalesceError(vdi, ce): 

844 """Reports a coalesce error to XenCenter. 

845 

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

847 ce: the CommandException that was raised""" 

848 

849 msg_name = os.strerror(ce.code) 

850 if ce.code == errno.ENOSPC: 

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

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

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

854 # actions. 

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

856 elif ce.code == errno.EIO: 

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

858 else: 

859 msg_body = '' 

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

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

862 

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

864 xapi = vdi.sr.xapi.session.xenapi 

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

866 oth_cfg = xapi.SR.get_other_config(sr_ref) 

867 if COALESCE_ERR_RATE_TAG in oth_cfg: 

868 coalesce_err_rate = float(oth_cfg[COALESCE_ERR_RATE_TAG]) 

869 else: 

870 coalesce_err_rate = DEFAULT_COALESCE_ERR_RATE 

871 

872 xcmsg = False 

873 if coalesce_err_rate == 0: 

874 xcmsg = True 

875 elif coalesce_err_rate > 0: 

876 now = datetime.datetime.now() 

877 sm_cfg = xapi.SR.get_sm_config(sr_ref) 

878 if COALESCE_LAST_ERR_TAG in sm_cfg: 

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

880 # messages in seconds) 

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

882 last = datetime.datetime.fromtimestamp( 

883 float(sm_cfg[COALESCE_LAST_ERR_TAG])) 

884 if now - last >= spm: 

885 xapi.SR.remove_from_sm_config(sr_ref, 

886 COALESCE_LAST_ERR_TAG) 

887 xcmsg = True 

888 else: 

889 xcmsg = True 

890 if xcmsg: 

891 xapi.SR.add_to_sm_config(sr_ref, COALESCE_LAST_ERR_TAG, 

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

893 if xcmsg: 

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

895 

896 def coalesce(self) -> int: 

897 # size is returned in sectors 

898 return vhdutil.coalesce(self.path) * 512 

899 

900 @staticmethod 

901 def _doCoalesceVHD(vdi): 

902 try: 

903 startTime = time.time() 

904 vhdSize = vdi.getAllocatedSize() 

905 coalesced_size = vdi.coalesce() 

906 endTime = time.time() 

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

908 except util.CommandException as ce: 

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

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

911 # reported by anyone. 

912 try: 

913 # Report coalesce errors back to user via XC 

914 VDI._reportCoalesceError(vdi, ce) 

915 except Exception as e: 

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

917 raise ce 

918 except: 

919 raise 

920 

921 def _vdi_is_raw(self, vdi_path): 

922 """ 

923 Given path to vdi determine if it is raw 

924 """ 

925 uuid = self.extractUuid(vdi_path) 

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

927 

928 def _coalesceVHD(self, timeOut): 

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

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

931 try: 

932 util.fistpoint.activate_custom_fn( 

933 "cleanup_coalesceVHD_inject_failure", 

934 util.inject_failure) 

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

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

937 except: 

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

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

940 # Try a repair and reraise the exception 

941 parent = "" 

942 try: 

943 parent = self.getParent() 

944 if not self._vdi_is_raw(parent): 

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

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

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

948 self.repair(parent) 

949 except Exception as e: 

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

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

952 (parent, self.path, e)) 

953 raise 

954 

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

956 

957 def _relinkSkip(self) -> None: 

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

959 abortFlag = IPCFlag(self.sr.uuid) 

960 for child in self.children: 

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

962 raise AbortException("Aborting due to signal") 

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

964 (child, self, self.parent)) 

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

966 child._setParent(self.parent) 

967 self.children = [] 

968 

969 def _reloadChildren(self, vdiSkip): 

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

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

972 abortFlag = IPCFlag(self.sr.uuid) 

973 for child in self.children: 

974 if child == vdiSkip: 

975 continue 

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

977 raise AbortException("Aborting due to signal") 

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

979 child._reload() 

980 

981 def _reload(self): 

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

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

984 child._reload() 

985 

986 # only leaves can be attached 

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

988 try: 

989 self.delConfig(VDI.DB_VDI_RELINKING) 

990 except XenAPI.Failure as e: 

991 if not util.isInvalidVDI(e): 

992 raise 

993 self.refresh() 

994 

995 def _tagChildrenForRelink(self): 

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

997 retries = 0 

998 try: 

999 while retries < 15: 

1000 retries += 1 

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

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

1003 self.uuid) 

1004 else: 

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

1006 

1007 if self.getConfig(VDI.DB_VDI_ACTIVATING): 

1008 self.delConfig(VDI.DB_VDI_RELINKING) 

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

1010 self.uuid) 

1011 else: 

1012 return 

1013 time.sleep(2) 

1014 

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

1016 except XenAPI.Failure as e: 

1017 if not util.isInvalidVDI(e): 

1018 raise 

1019 

1020 for child in self.children: 

1021 child._tagChildrenForRelink() 

1022 

1023 def _loadInfoParent(self): 

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

1025 if ret: 

1026 self.parentUuid = ret 

1027 

1028 def _setParent(self, parent) -> None: 

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

1030 self.parent = parent 

1031 self.parentUuid = parent.uuid 

1032 parent.children.append(self) 

1033 try: 

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

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

1036 (self.uuid, self.parentUuid)) 

1037 except: 

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

1039 (self.uuid, self.parentUuid)) 

1040 

1041 def _loadInfoHidden(self) -> None: 

1042 hidden = vhdutil.getHidden(self.path) 

1043 self.hidden = (hidden != 0) 

1044 

1045 def _setHidden(self, hidden=True) -> None: 

1046 vhdutil.setHidden(self.path, hidden) 

1047 self.hidden = hidden 

1048 

1049 def _increaseSizeVirt(self, size, atomic=True) -> None: 

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

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

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

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

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

1055 call with atomic = False""" 

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

1057 return 

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

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

1060 

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

1062 if (size <= msize): 

1063 vhdutil.setSizeVirtFast(self.path, size) 

1064 else: 

1065 if atomic: 

1066 vdiList = self._getAllSubtree() 

1067 self.sr.lock() 

1068 try: 

1069 self.sr.pauseVDIs(vdiList) 

1070 try: 

1071 self._setSizeVirt(size) 

1072 finally: 

1073 self.sr.unpauseVDIs(vdiList) 

1074 finally: 

1075 self.sr.unlock() 

1076 else: 

1077 self._setSizeVirt(size) 

1078 

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

1080 

1081 def _setSizeVirt(self, size) -> None: 

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

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

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

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

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

1087 

1088 def _queryVHDBlocks(self) -> bytes: 

1089 return vhdutil.getBlockBitmap(self.path) 

1090 

1091 def _getCoalescedSizeData(self): 

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

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

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

1095 upper bound)""" 

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

1097 # was writable all this time 

1098 self.delConfig(VDI.DB_VHD_BLOCKS) 

1099 blocksChild = self.getVHDBlocks() 

1100 blocksParent = self.parent.getVHDBlocks() 

1101 numBlocks = Util.countBits(blocksChild, blocksParent) 

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

1103 sizeData = numBlocks * vhdutil.VHD_BLOCK_SIZE 

1104 assert(sizeData <= self.sizeVirt) 

1105 return sizeData 

1106 

1107 def _calcExtraSpaceForCoalescing(self) -> int: 

1108 sizeData = self._getCoalescedSizeData() 

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

1110 vhdutil.calcOverheadEmpty(self.sizeVirt) 

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

1112 return sizeCoalesced - self.parent.getSizeVHD() 

1113 

1114 def _calcExtraSpaceForLeafCoalescing(self) -> int: 

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

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

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

1118 return self._calcExtraSpaceForCoalescing() 

1119 

1120 def _calcExtraSpaceForSnapshotCoalescing(self) -> int: 

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

1122 snapshot-coalesce this VDI""" 

1123 return self._calcExtraSpaceForCoalescing() + \ 

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

1125 

1126 def _getAllSubtree(self): 

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

1128 vdiList = [self] 

1129 for child in self.children: 

1130 vdiList.extend(child._getAllSubtree()) 

1131 return vdiList 

1132 

1133 

1134class FileVDI(VDI): 

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

1136 

1137 @staticmethod 

1138 def extractUuid(path): 

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

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

1141 path.endswith(vhdutil.FILE_EXTN_RAW)): 

1142 return None 

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

1144 vhdutil.FILE_EXTN_RAW, "") 

1145 # TODO: validate UUID format 

1146 return uuid 

1147 

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

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

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

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

1152 else: 

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

1154 

1155 @override 

1156 def load(self, info=None) -> None: 

1157 if not info: 

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

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

1160 try: 

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

1162 except util.SMException: 

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

1164 return 

1165 self.parent = None 

1166 self.children = [] 

1167 self.parentUuid = info.parentUuid 

1168 self.sizeVirt = info.sizeVirt 

1169 self._sizeVHD = info.sizePhys 

1170 self._sizeAllocated = info.sizeAllocated 

1171 self.hidden = info.hidden 

1172 self.scanError = False 

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

1174 (self.uuid, vhdutil.FILE_EXTN_VHD)) 

1175 

1176 @override 

1177 def rename(self, uuid) -> None: 

1178 oldPath = self.path 

1179 VDI.rename(self, uuid) 

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

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

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

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

1184 os.rename(oldPath, self.path) 

1185 

1186 @override 

1187 def delete(self) -> None: 

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

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

1190 self.uuid) 

1191 try: 

1192 self.sr.lock() 

1193 try: 

1194 os.unlink(self.path) 

1195 self.sr.forgetVDI(self.uuid) 

1196 finally: 

1197 self.sr.unlock() 

1198 except OSError: 

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

1200 VDI.delete(self) 

1201 

1202 

1203class LVHDVDI(VDI): 

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

1205 

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

1207 DRIVER_NAME_RAW = "aio" 

1208 

1209 @override 

1210 def load(self, info=None) -> None: 

1211 # `info` is always set. `None` default value is only here to match parent method. 

1212 assert info, "No info given to LVHDVDI.load" 

1213 self.parent = None 

1214 self.children = [] 

1215 self._sizeVHD = -1 

1216 self._sizeAllocated = -1 

1217 self.scanError = info.scanError 

1218 self.sizeLV = info.sizeLV 

1219 self.sizeVirt = info.sizeVirt 

1220 self.fileName = info.lvName 

1221 self.lvActive = info.lvActive 

1222 self.lvOpen = info.lvOpen 

1223 self.lvReadonly = info.lvReadonly 

1224 self.hidden = info.hidden 

1225 self.parentUuid = info.parentUuid 

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

1227 

1228 @staticmethod 

1229 def extractUuid(path): 

1230 return lvhdutil.extractUuid(path) 

1231 

1232 @override 

1233 def getDriverName(self) -> str: 

1234 if self.raw: 

1235 return self.DRIVER_NAME_RAW 

1236 return self.DRIVER_NAME_VHD 

1237 

1238 def inflate(self, size): 

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

1240 if self.raw: 

1241 return 

1242 self._activate() 

1243 self.sr.lock() 

1244 try: 

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

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

1247 finally: 

1248 self.sr.unlock() 

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

1250 self._sizeVHD = -1 

1251 self._sizeAllocated = -1 

1252 

1253 def deflate(self): 

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

1255 if self.raw: 

1256 return 

1257 self._activate() 

1258 self.sr.lock() 

1259 try: 

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

1261 finally: 

1262 self.sr.unlock() 

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

1264 self._sizeVHD = -1 

1265 self._sizeAllocated = -1 

1266 

1267 def inflateFully(self): 

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

1269 

1270 def inflateParentForCoalesce(self): 

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

1272 coalescing""" 

1273 if self.parent.raw: 

1274 return 

1275 inc = self._calcExtraSpaceForCoalescing() 

1276 if inc > 0: 

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

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

1279 

1280 @override 

1281 def updateBlockInfo(self) -> Optional[str]: 

1282 if not self.raw: 

1283 return VDI.updateBlockInfo(self) 

1284 return None 

1285 

1286 @override 

1287 def rename(self, uuid) -> None: 

1288 oldUuid = self.uuid 

1289 oldLVName = self.fileName 

1290 VDI.rename(self, uuid) 

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

1292 if self.raw: 

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

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

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

1296 

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

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

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

1300 

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

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

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

1304 RefCounter.reset(oldUuid, ns) 

1305 

1306 @override 

1307 def delete(self) -> None: 

1308 if len(self.children) > 0: 

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

1310 self.uuid) 

1311 self.sr.lock() 

1312 try: 

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

1314 self.sr.forgetVDI(self.uuid) 

1315 finally: 

1316 self.sr.unlock() 

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

1318 VDI.delete(self) 

1319 

1320 @override 

1321 def getSizeVHD(self) -> int: 

1322 if self._sizeVHD == -1: 

1323 self._loadInfoSizeVHD() 

1324 return self._sizeVHD 

1325 

1326 def _loadInfoSizeVHD(self): 

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

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

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

1330 coalescing.""" 

1331 if self.raw: 

1332 return 

1333 self._activate() 

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

1335 if self._sizeVHD <= 0: 

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

1337 (self, self._sizeVHD)) 

1338 

1339 @override 

1340 def getAllocatedSize(self) -> int: 

1341 if self._sizeAllocated == -1: 

1342 self._loadInfoSizeAllocated() 

1343 return self._sizeAllocated 

1344 

1345 def _loadInfoSizeAllocated(self): 

1346 """ 

1347 Get the allocated size of the VHD volume. 

1348 """ 

1349 if self.raw: 

1350 return 

1351 self._activate() 

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

1353 

1354 @override 

1355 def _loadInfoHidden(self) -> None: 

1356 if self.raw: 

1357 self.hidden = self.sr.lvmCache.getHidden(self.fileName) 

1358 else: 

1359 VDI._loadInfoHidden(self) 

1360 

1361 @override 

1362 def _setHidden(self, hidden=True) -> None: 

1363 if self.raw: 

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

1365 self.hidden = hidden 

1366 else: 

1367 VDI._setHidden(self, hidden) 

1368 

1369 @override 

1370 def __str__(self) -> str: 

1371 strType = "VHD" 

1372 if self.raw: 

1373 strType = "RAW" 

1374 strHidden = "" 

1375 if self.hidden: 

1376 strHidden = "*" 

1377 strSizeVHD = "" 

1378 if self._sizeVHD > 0: 

1379 strSizeVHD = Util.num2str(self._sizeVHD) 

1380 strSizeAllocated = "" 

1381 if self._sizeAllocated >= 0: 

1382 strSizeAllocated = Util.num2str(self._sizeAllocated) 

1383 strActive = "n" 

1384 if self.lvActive: 

1385 strActive = "a" 

1386 if self.lvOpen: 

1387 strActive += "o" 

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

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

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

1391 

1392 @override 

1393 def validate(self, fast=False) -> None: 

1394 if not self.raw: 

1395 VDI.validate(self, fast) 

1396 

1397 @override 

1398 def _doCoalesce(self) -> None: 

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

1400 try: 

1401 self._activateChain() 

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

1403 self.parent.validate() 

1404 self.inflateParentForCoalesce() 

1405 VDI._doCoalesce(self) 

1406 finally: 

1407 self.parent._loadInfoSizeVHD() 

1408 self.parent.deflate() 

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

1410 

1411 @override 

1412 def _setParent(self, parent) -> None: 

1413 self._activate() 

1414 if self.lvReadonly: 

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

1416 

1417 try: 

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

1419 finally: 

1420 if self.lvReadonly: 

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

1422 self._deactivate() 

1423 self.parent = parent 

1424 self.parentUuid = parent.uuid 

1425 parent.children.append(self) 

1426 try: 

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

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

1429 (self.uuid, self.parentUuid)) 

1430 except: 

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

1432 (self.parentUuid, self.uuid)) 

1433 

1434 def _activate(self): 

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

1436 

1437 def _activateChain(self): 

1438 vdi = self 

1439 while vdi: 

1440 vdi._activate() 

1441 vdi = vdi.parent 

1442 

1443 def _deactivate(self): 

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

1445 

1446 @override 

1447 def _increaseSizeVirt(self, size, atomic=True) -> None: 

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

1449 self._activate() 

1450 if not self.raw: 

1451 VDI._increaseSizeVirt(self, size, atomic) 

1452 return 

1453 

1454 # raw VDI case 

1455 offset = self.sizeLV 

1456 if self.sizeVirt < size: 

1457 oldSize = self.sizeLV 

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

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

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

1461 offset = oldSize 

1462 unfinishedZero = False 

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

1464 if jval: 

1465 unfinishedZero = True 

1466 offset = int(jval) 

1467 length = self.sizeLV - offset 

1468 if not length: 

1469 return 

1470 

1471 if unfinishedZero: 

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

1473 else: 

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

1475 str(offset)) 

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

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

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

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

1480 VDI.POLL_INTERVAL, 0) 

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

1482 

1483 @override 

1484 def _setSizeVirt(self, size) -> None: 

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

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

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

1488 self._activate() 

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

1490 vhdutil.MAX_VHD_JOURNAL_SIZE) 

1491 try: 

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

1493 size, jFile) 

1494 finally: 

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

1496 

1497 @override 

1498 def _queryVHDBlocks(self) -> bytes: 

1499 self._activate() 

1500 return VDI._queryVHDBlocks(self) 

1501 

1502 @override 

1503 def _calcExtraSpaceForCoalescing(self) -> int: 

1504 if self.parent.raw: 

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

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

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

1508 return sizeCoalesced - self.parent.sizeLV 

1509 

1510 @override 

1511 def _calcExtraSpaceForLeafCoalescing(self) -> int: 

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

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

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

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

1516 return self._calcExtraSpaceForCoalescing() - deflateDiff 

1517 

1518 @override 

1519 def _calcExtraSpaceForSnapshotCoalescing(self) -> int: 

1520 return self._calcExtraSpaceForCoalescing() + \ 

1521 lvhdutil.calcSizeLV(self.getSizeVHD()) 

1522 

1523 

1524class LinstorVDI(VDI): 

1525 """Object representing a VDI in a LINSTOR SR""" 

1526 

1527 VOLUME_LOCK_TIMEOUT = 30 

1528 

1529 @override 

1530 def load(self, info=None) -> None: 

1531 self.parentUuid = info.parentUuid 

1532 self.scanError = True 

1533 self.parent = None 

1534 self.children = [] 

1535 

1536 self.fileName = self.sr._linstor.get_volume_name(self.uuid) 

1537 self.path = self.sr._linstor.build_device_path(self.fileName) 

1538 

1539 if not info: 

1540 try: 

1541 info = self.sr._vhdutil.get_vhd_info(self.uuid) 

1542 except util.SMException: 

1543 Util.log( 

1544 ' [VDI {}: failed to read VHD metadata]'.format(self.uuid) 

1545 ) 

1546 return 

1547 

1548 self.parentUuid = info.parentUuid 

1549 self.sizeVirt = info.sizeVirt 

1550 self._sizeVHD = -1 

1551 self._sizeAllocated = -1 

1552 self.drbd_size = -1 

1553 self.hidden = info.hidden 

1554 self.scanError = False 

1555 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1556 

1557 @override 

1558 def getSizeVHD(self, fetch=False) -> int: 

1559 if self._sizeVHD < 0 or fetch: 

1560 self._sizeVHD = self.sr._vhdutil.get_size_phys(self.uuid) 

1561 return self._sizeVHD 

1562 

1563 def getDrbdSize(self, fetch=False): 

1564 if self.drbd_size < 0 or fetch: 

1565 self.drbd_size = self.sr._vhdutil.get_drbd_size(self.uuid) 

1566 return self.drbd_size 

1567 

1568 @override 

1569 def getAllocatedSize(self) -> int: 

1570 if self._sizeAllocated == -1: 

1571 if not self.raw: 

1572 self._sizeAllocated = self.sr._vhdutil.get_allocated_size(self.uuid) 

1573 return self._sizeAllocated 

1574 

1575 def inflate(self, size): 

1576 if self.raw: 

1577 return 

1578 self.sr.lock() 

1579 try: 

1580 # Ensure we use the real DRBD size and not the cached one. 

1581 # Why? Because this attribute can be changed if volume is resized by user. 

1582 self.drbd_size = self.getDrbdSize(fetch=True) 

1583 self.sr._vhdutil.inflate(self.sr.journaler, self.uuid, self.path, size, self.drbd_size) 

1584 finally: 

1585 self.sr.unlock() 

1586 self.drbd_size = -1 

1587 self._sizeVHD = -1 

1588 self._sizeAllocated = -1 

1589 

1590 def deflate(self): 

1591 if self.raw: 

1592 return 

1593 self.sr.lock() 

1594 try: 

1595 # Ensure we use the real sizes and not the cached info. 

1596 self.drbd_size = self.getDrbdSize(fetch=True) 

1597 self._sizeVHD = self.getSizeVHD(fetch=True) 

1598 self.sr._vhdutil.force_deflate(self.path, self._sizeVHD, self.drbd_size, zeroize=False) 

1599 finally: 

1600 self.sr.unlock() 

1601 self.drbd_size = -1 

1602 self._sizeVHD = -1 

1603 self._sizeAllocated = -1 

1604 

1605 def inflateFully(self): 

1606 if not self.raw: 

1607 self.inflate(LinstorVhdUtil.compute_volume_size(self.sizeVirt, self.vdi_type)) 

1608 

1609 @override 

1610 def rename(self, uuid) -> None: 

1611 Util.log('Renaming {} -> {} (path={})'.format( 

1612 self.uuid, uuid, self.path 

1613 )) 

1614 self.sr._linstor.update_volume_uuid(self.uuid, uuid) 

1615 VDI.rename(self, uuid) 

1616 

1617 @override 

1618 def delete(self) -> None: 

1619 if len(self.children) > 0: 

1620 raise util.SMException( 

1621 'VDI {} has children, can\'t delete'.format(self.uuid) 

1622 ) 

1623 self.sr.lock() 

1624 try: 

1625 self.sr._linstor.destroy_volume(self.uuid) 

1626 self.sr.forgetVDI(self.uuid) 

1627 finally: 

1628 self.sr.unlock() 

1629 VDI.delete(self) 

1630 

1631 @override 

1632 def validate(self, fast=False) -> None: 

1633 if not self.raw and not self.sr._vhdutil.check(self.uuid, fast=fast): 

1634 raise util.SMException('VHD {} corrupted'.format(self)) 

1635 

1636 @override 

1637 def pause(self, failfast=False) -> None: 

1638 self.sr._linstor.ensure_volume_is_not_locked( 

1639 self.uuid, timeout=self.VOLUME_LOCK_TIMEOUT 

1640 ) 

1641 return super(LinstorVDI, self).pause(failfast) 

1642 

1643 @override 

1644 def coalesce(self) -> int: 

1645 # Note: We raise `SMException` here to skip the current coalesce in case of failure. 

1646 # Using another exception we can't execute the next coalesce calls. 

1647 return self.sr._vhdutil.force_coalesce(self.path) * 512 

1648 

1649 @override 

1650 def getParent(self) -> str: 

1651 return self.sr._vhdutil.get_parent( 

1652 self.sr._linstor.get_volume_uuid_from_device_path(self.path) 

1653 ) 

1654 

1655 @override 

1656 def repair(self, parent_uuid) -> None: 

1657 self.sr._vhdutil.force_repair( 

1658 self.sr._linstor.get_device_path(parent_uuid) 

1659 ) 

1660 

1661 @override 

1662 def _relinkSkip(self) -> None: 

1663 abortFlag = IPCFlag(self.sr.uuid) 

1664 for child in self.children: 

1665 if abortFlag.test(FLAG_TYPE_ABORT): 

1666 raise AbortException('Aborting due to signal') 

1667 Util.log( 

1668 ' Relinking {} from {} to {}'.format( 

1669 child, self, self.parent 

1670 ) 

1671 ) 

1672 

1673 session = child.sr.xapi.session 

1674 sr_uuid = child.sr.uuid 

1675 vdi_uuid = child.uuid 

1676 try: 

1677 self.sr._linstor.ensure_volume_is_not_locked( 

1678 vdi_uuid, timeout=self.VOLUME_LOCK_TIMEOUT 

1679 ) 

1680 blktap2.VDI.tap_pause(session, sr_uuid, vdi_uuid) 

1681 child._setParent(self.parent) 

1682 finally: 

1683 blktap2.VDI.tap_unpause(session, sr_uuid, vdi_uuid) 

1684 self.children = [] 

1685 

1686 @override 

1687 def _setParent(self, parent) -> None: 

1688 self.sr._linstor.get_device_path(self.uuid) 

1689 self.sr._vhdutil.force_parent(self.path, parent.path) 

1690 self.parent = parent 

1691 self.parentUuid = parent.uuid 

1692 parent.children.append(self) 

1693 try: 

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

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

1696 (self.uuid, self.parentUuid)) 

1697 except: 

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

1699 (self.uuid, self.parentUuid)) 

1700 

1701 @override 

1702 def _doCoalesce(self) -> None: 

1703 try: 

1704 self._activateChain() 

1705 self.parent.validate() 

1706 self._inflateParentForCoalesce() 

1707 VDI._doCoalesce(self) 

1708 finally: 

1709 self.parent.deflate() 

1710 

1711 def _activateChain(self): 

1712 vdi = self 

1713 while vdi: 

1714 try: 

1715 p = self.sr._linstor.get_device_path(vdi.uuid) 

1716 except Exception as e: 

1717 # Use SMException to skip coalesce. 

1718 # Otherwise the GC is stopped... 

1719 raise util.SMException(str(e)) 

1720 vdi = vdi.parent 

1721 

1722 @override 

1723 def _setHidden(self, hidden=True) -> None: 

1724 HIDDEN_TAG = 'hidden' 

1725 

1726 if self.raw: 

1727 self.sr._linstor.update_volume_metadata(self.uuid, { 

1728 HIDDEN_TAG: hidden 

1729 }) 

1730 self.hidden = hidden 

1731 else: 

1732 VDI._setHidden(self, hidden) 

1733 

1734 @override 

1735 def _setSizeVirt(self, size) -> None: 

1736 jfile = self.uuid + '-jvhd' 

1737 self.sr._linstor.create_volume( 

1738 jfile, vhdutil.MAX_VHD_JOURNAL_SIZE, persistent=False, volume_name=jfile 

1739 ) 

1740 try: 

1741 self.inflate(LinstorVhdUtil.compute_volume_size(size, self.vdi_type)) 

1742 self.sr._vhdutil.set_size_virt(size, jfile) 

1743 finally: 

1744 try: 

1745 self.sr._linstor.destroy_volume(jfile) 

1746 except Exception: 

1747 # We can ignore it, in any case this volume is not persistent. 

1748 pass 

1749 

1750 @override 

1751 def _queryVHDBlocks(self) -> bytes: 

1752 return self.sr._vhdutil.get_block_bitmap(self.uuid) 

1753 

1754 def _inflateParentForCoalesce(self): 

1755 if self.parent.raw: 

1756 return 

1757 inc = self._calcExtraSpaceForCoalescing() 

1758 if inc > 0: 

1759 self.parent.inflate(self.parent.getDrbdSize() + inc) 

1760 

1761 @override 

1762 def _calcExtraSpaceForCoalescing(self) -> int: 

1763 if self.parent.raw: 

1764 return 0 

1765 size_coalesced = LinstorVhdUtil.compute_volume_size( 

1766 self._getCoalescedSizeData(), self.vdi_type 

1767 ) 

1768 Util.log("Coalesced size = %s" % Util.num2str(size_coalesced)) 

1769 return size_coalesced - self.parent.getDrbdSize() 

1770 

1771 @override 

1772 def _calcExtraSpaceForLeafCoalescing(self) -> int: 

1773 assert self.getDrbdSize() > 0 

1774 assert self.getSizeVHD() > 0 

1775 deflate_diff = self.getDrbdSize() - LinstorVolumeManager.round_up_volume_size(self.getSizeVHD()) 

1776 assert deflate_diff >= 0 

1777 return self._calcExtraSpaceForCoalescing() - deflate_diff 

1778 

1779 @override 

1780 def _calcExtraSpaceForSnapshotCoalescing(self) -> int: 

1781 assert self.getSizeVHD() > 0 

1782 return self._calcExtraSpaceForCoalescing() + \ 

1783 LinstorVolumeManager.round_up_volume_size(self.getSizeVHD()) 

1784 

1785################################################################################ 

1786# 

1787# SR 

1788# 

1789class SR(object): 

1790 class LogFilter: 

1791 def __init__(self, sr): 

1792 self.sr = sr 

1793 self.stateLogged = False 

1794 self.prevState = {} 

1795 self.currState = {} 

1796 

1797 def logState(self): 

1798 changes = "" 

1799 self.currState.clear() 

1800 for vdi in self.sr.vdiTrees: 

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

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

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

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

1805 

1806 for uuid in self.prevState: 

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

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

1809 

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

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

1812 

1813 if len(changes) > 0: 

1814 if self.stateLogged: 

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

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

1817 else: 

1818 result += "no changes" 

1819 

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

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

1822 self.prevState.clear() 

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

1824 self.prevState[key] = val 

1825 self.stateLogged = True 

1826 

1827 def logNewVDI(self, uuid): 

1828 if self.stateLogged: 

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

1830 

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

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

1833 for child in vdi.children: 

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

1835 return treeStr 

1836 

1837 TYPE_FILE = "file" 

1838 TYPE_LVHD = "lvhd" 

1839 TYPE_LINSTOR = "linstor" 

1840 TYPES = [TYPE_LVHD, TYPE_FILE, TYPE_LINSTOR] 

1841 

1842 LOCK_RETRY_INTERVAL = 3 

1843 LOCK_RETRY_ATTEMPTS = 20 

1844 LOCK_RETRY_ATTEMPTS_LOCK = 100 

1845 

1846 SCAN_RETRY_ATTEMPTS = 3 

1847 

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

1849 TMP_RENAME_PREFIX = "OLD_" 

1850 

1851 KEY_OFFLINE_COALESCE_NEEDED = "leaf_coalesce_need_offline" 

1852 KEY_OFFLINE_COALESCE_OVERRIDE = "leaf_coalesce_offline_override" 

1853 

1854 @staticmethod 

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

1856 xapi = XAPI(xapiSession, uuid) 

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

1858 if type == SR.TYPE_FILE: 

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

1860 elif type == SR.TYPE_LVHD: 

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

1862 elif type == SR.TYPE_LINSTOR: 

1863 return LinstorSR(uuid, xapi, createLock, force) 

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

1865 

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

1867 self.logFilter = self.LogFilter(self) 

1868 self.uuid = uuid 

1869 self.path = "" 

1870 self.name = "" 

1871 self.vdis = {} 

1872 self.vdiTrees = [] 

1873 self.journaler = None 

1874 self.xapi = xapi 

1875 self._locked = 0 

1876 self._srLock = None 

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

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

1879 else: 

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

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

1882 self._failedCoalesceTargets = [] 

1883 

1884 if not self.xapi.isPluggedHere(): 

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

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

1887 else: 

1888 if not self.wait_for_plug(): 

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

1890 

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

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

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

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

1895 

1896 def wait_for_plug(self): 

1897 for _ in range(1, 10): 

1898 time.sleep(2) 

1899 if self.xapi.isPluggedHere(): 

1900 return True 

1901 return False 

1902 

1903 def gcEnabled(self, refresh=True): 

1904 if refresh: 

1905 self.xapi.srRecord = \ 

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

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

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

1909 return False 

1910 return True 

1911 

1912 def scan(self, force=False) -> None: 

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

1914 update VDI objects if they already exist""" 

1915 pass 

1916 

1917 def scanLocked(self, force=False): 

1918 self.lock() 

1919 try: 

1920 self.scan(force) 

1921 finally: 

1922 self.unlock() 

1923 

1924 def getVDI(self, uuid): 

1925 return self.vdis.get(uuid) 

1926 

1927 def hasWork(self): 

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

1929 return True 

1930 if self.findCoalesceable(): 

1931 return True 

1932 if self.findLeafCoalesceable(): 

1933 return True 

1934 if self.needUpdateBlockInfo(): 

1935 return True 

1936 return False 

1937 

1938 def findCoalesceable(self): 

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

1940 (choosing one among all coalesceable candidates according to some 

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

1942 

1943 candidates = [] 

1944 

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

1946 if srSwitch == "false": 

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

1948 return candidates 

1949 

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

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

1952 for uuid in journals: 

1953 vdi = self.getVDI(uuid) 

1954 if vdi and vdi not in self._failedCoalesceTargets: 

1955 return vdi 

1956 

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

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

1959 candidates.append(vdi) 

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

1961 

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

1963 

1964 # pick one in the tallest tree 

1965 treeHeight = dict() 

1966 for c in candidates: 

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

1968 if treeHeight.get(height): 

1969 treeHeight[height].append(c) 

1970 else: 

1971 treeHeight[height] = [c] 

1972 

1973 freeSpace = self.getFreeSpace() 

1974 heights = list(treeHeight.keys()) 

1975 heights.sort(reverse=True) 

1976 for h in heights: 

1977 for c in treeHeight[h]: 

1978 spaceNeeded = c._calcExtraSpaceForCoalescing() 

1979 if spaceNeeded <= freeSpace: 

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

1981 return c 

1982 else: 

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

1984 (c, freeSpace)) 

1985 return None 

1986 

1987 def getSwitch(self, key): 

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

1989 

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

1991 srSwitch = self.getSwitch(switch) 

1992 ret = False 

1993 if srSwitch: 

1994 ret = srSwitch == condition 

1995 

1996 if ret: 

1997 Util.log(fail_msg) 

1998 

1999 return ret 

2000 

2001 def leafCoalesceForbidden(self): 

2002 return (self.forbiddenBySwitch(VDI.DB_COALESCE, 

2003 "false", 

2004 "Coalesce disabled for this SR") or 

2005 self.forbiddenBySwitch(VDI.DB_LEAFCLSC, 

2006 VDI.LEAFCLSC_DISABLED, 

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

2008 

2009 def findLeafCoalesceable(self): 

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

2011 

2012 candidates = [] 

2013 if self.leafCoalesceForbidden(): 

2014 return candidates 

2015 

2016 self.gatherLeafCoalesceable(candidates) 

2017 

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

2019 

2020 freeSpace = self.getFreeSpace() 

2021 for candidate in candidates: 

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

2023 # feasible for this candidate 

2024 spaceNeeded = candidate._calcExtraSpaceForSnapshotCoalescing() 

2025 spaceNeededLive = spaceNeeded 

2026 if spaceNeeded > freeSpace: 

2027 spaceNeededLive = candidate._calcExtraSpaceForLeafCoalescing() 

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

2029 spaceNeeded = spaceNeededLive 

2030 

2031 if spaceNeeded <= freeSpace: 

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

2033 return candidate 

2034 else: 

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

2036 (candidate, freeSpace)) 

2037 if spaceNeededLive <= freeSpace: 

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

2039 candidate.setConfig(VDI.DB_LEAFCLSC, 

2040 VDI.LEAFCLSC_OFFLINE) 

2041 

2042 return None 

2043 

2044 def gatherLeafCoalesceable(self, candidates): 

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

2046 if not vdi.isLeafCoalesceable(): 

2047 continue 

2048 if vdi in self._failedCoalesceTargets: 

2049 continue 

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

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

2052 continue 

2053 if vdi.getConfig(vdi.DB_ALLOW_CACHING): 

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

2055 continue 

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

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

2058 continue 

2059 if not (AUTO_ONLINE_LEAF_COALESCE_ENABLED or 

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

2061 continue 

2062 candidates.append(vdi) 

2063 

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

2065 """Coalesce vdi onto parent""" 

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

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

2068 return 

2069 

2070 try: 

2071 self._coalesce(vdi) 

2072 except util.SMException as e: 

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

2074 self.cleanup() 

2075 raise 

2076 else: 

2077 self._failedCoalesceTargets.append(vdi) 

2078 Util.logException("coalesce") 

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

2080 self.cleanup() 

2081 

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

2083 """Leaf-coalesce vdi onto parent""" 

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

2085 if dryRun: 

2086 return 

2087 

2088 try: 

2089 uuid = vdi.uuid 

2090 try: 

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

2092 self._coalesceLeaf(vdi) 

2093 finally: 

2094 vdi = self.getVDI(uuid) 

2095 if vdi: 

2096 vdi.delConfig(vdi.DB_LEAFCLSC) 

2097 except AbortException: 

2098 self.cleanup() 

2099 raise 

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

2101 self._failedCoalesceTargets.append(vdi) 

2102 Util.logException("leaf-coalesce") 

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

2104 self.cleanup() 

2105 

2106 def garbageCollect(self, dryRun=False): 

2107 vdiList = self.findGarbage() 

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

2109 for vdi in vdiList: 

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

2111 if not dryRun: 

2112 self.deleteVDIs(vdiList) 

2113 self.cleanupJournals(dryRun) 

2114 

2115 def findGarbage(self): 

2116 vdiList = [] 

2117 for vdi in self.vdiTrees: 

2118 vdiList.extend(vdi.getAllPrunable()) 

2119 return vdiList 

2120 

2121 def deleteVDIs(self, vdiList) -> None: 

2122 for vdi in vdiList: 

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

2124 raise AbortException("Aborting due to signal") 

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

2126 self.deleteVDI(vdi) 

2127 

2128 def deleteVDI(self, vdi) -> None: 

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

2130 del self.vdis[vdi.uuid] 

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

2132 vdi.parent.children.remove(vdi) 

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

2134 self.vdiTrees.remove(vdi) 

2135 vdi.delete() 

2136 

2137 def forgetVDI(self, vdiUuid) -> None: 

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

2139 

2140 def pauseVDIs(self, vdiList) -> None: 

2141 paused = [] 

2142 failed = False 

2143 for vdi in vdiList: 

2144 try: 

2145 vdi.pause() 

2146 paused.append(vdi) 

2147 except: 

2148 Util.logException("pauseVDIs") 

2149 failed = True 

2150 break 

2151 

2152 if failed: 

2153 self.unpauseVDIs(paused) 

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

2155 

2156 def unpauseVDIs(self, vdiList): 

2157 failed = False 

2158 for vdi in vdiList: 

2159 try: 

2160 vdi.unpause() 

2161 except: 

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

2163 failed = True 

2164 if failed: 

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

2166 

2167 def getFreeSpace(self) -> int: 

2168 return 0 

2169 

2170 def cleanup(self): 

2171 Util.log("In cleanup") 

2172 return 

2173 

2174 @override 

2175 def __str__(self) -> str: 

2176 if self.name: 

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

2178 else: 

2179 ret = "%s" % self.uuid 

2180 return ret 

2181 

2182 def lock(self): 

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

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

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

2186 if not self._srLock: 

2187 return 

2188 

2189 if self._locked == 0: 

2190 abortFlag = IPCFlag(self.uuid) 

2191 for i in range(SR.LOCK_RETRY_ATTEMPTS_LOCK): 

2192 if self._srLock.acquireNoblock(): 

2193 self._locked += 1 

2194 return 

2195 if abortFlag.test(FLAG_TYPE_ABORT): 

2196 raise AbortException("Abort requested") 

2197 time.sleep(SR.LOCK_RETRY_INTERVAL) 

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

2199 

2200 self._locked += 1 

2201 

2202 def unlock(self): 

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

2204 return 

2205 assert(self._locked > 0) 

2206 self._locked -= 1 

2207 if self._locked == 0: 

2208 self._srLock.release() 

2209 

2210 def needUpdateBlockInfo(self) -> bool: 

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

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

2213 continue 

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

2215 return True 

2216 return False 

2217 

2218 def updateBlockInfo(self) -> None: 

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

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

2221 continue 

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

2223 vdi.updateBlockInfo() 

2224 

2225 def cleanupCoalesceJournals(self): 

2226 """Remove stale coalesce VDI indicators""" 

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

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

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

2230 

2231 def cleanupJournals(self, dryRun=False): 

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

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

2234 entries = self.journaler.getAll(t) 

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

2236 if self.getVDI(uuid): 

2237 continue 

2238 if t == SR.JRN_CLONE: 

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

2240 if self.getVDI(baseUuid): 

2241 continue 

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

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

2244 if not dryRun: 

2245 self.journaler.remove(t, uuid) 

2246 

2247 def cleanupCache(self, maxAge=-1) -> int: 

2248 return 0 

2249 

2250 def _coalesce(self, vdi): 

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

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

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

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

2255 else: 

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

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

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

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

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

2261 vdi._doCoalesce() 

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

2263 

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

2265 

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

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

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

2269 # scan 

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

2271 

2272 self.lock() 

2273 try: 

2274 vdi.parent._tagChildrenForRelink() 

2275 self.scan() 

2276 vdi._relinkSkip() 

2277 finally: 

2278 self.unlock() 

2279 # Reload the children to leave things consistent 

2280 vdi.parent._reloadChildren(vdi) 

2281 

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

2283 self.deleteVDI(vdi) 

2284 

2285 class CoalesceTracker: 

2286 GRACE_ITERATIONS = 1 

2287 MAX_ITERATIONS_NO_PROGRESS = 3 

2288 MAX_ITERATIONS = 10 

2289 MAX_INCREASE_FROM_MINIMUM = 1.2 

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

2291 " --> Final size {finSize}" 

2292 

2293 def __init__(self, sr): 

2294 self.itsNoProgress = 0 

2295 self.its = 0 

2296 self.minSize = float("inf") 

2297 self.history = [] 

2298 self.reason = "" 

2299 self.startSize = None 

2300 self.finishSize = None 

2301 self.sr = sr 

2302 

2303 def abortCoalesce(self, prevSize, curSize): 

2304 res = False 

2305 

2306 self.its += 1 

2307 self.history.append(self.HISTORY_STRING.format(its=self.its, 

2308 initSize=prevSize, 

2309 finSize=curSize)) 

2310 

2311 self.finishSize = curSize 

2312 

2313 if self.startSize is None: 

2314 self.startSize = prevSize 

2315 

2316 if curSize < self.minSize: 

2317 self.minSize = curSize 

2318 

2319 if prevSize < self.minSize: 

2320 self.minSize = prevSize 

2321 

2322 if prevSize < curSize: 

2323 self.itsNoProgress += 1 

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

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

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

2327 

2328 if (not res) and (self.its > self.MAX_ITERATIONS): 

2329 max = self.MAX_ITERATIONS 

2330 self.reason = \ 

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

2332 res = True 

2333 

2334 if (not res) and (self.itsNoProgress > 

2335 self.MAX_ITERATIONS_NO_PROGRESS): 

2336 max = self.MAX_ITERATIONS_NO_PROGRESS 

2337 self.reason = \ 

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

2339 res = True 

2340 

2341 maxSizeFromMin = self.MAX_INCREASE_FROM_MINIMUM * self.minSize 

2342 if (self.its > self.GRACE_ITERATIONS and 

2343 (not res) and (curSize > maxSizeFromMin)): 

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

2345 " compared to minimum acheived" 

2346 res = True 

2347 

2348 return res 

2349 

2350 def printReasoning(self): 

2351 Util.log("Aborted coalesce") 

2352 for hist in self.history: 

2353 Util.log(hist) 

2354 Util.log(self.reason) 

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

2356 .format(size=self.startSize)) 

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

2358 .format(size=self.finishSize)) 

2359 Util.log("Minimum size acheived was {size}" 

2360 .format(size=self.minSize)) 

2361 

2362 def _coalesceLeaf(self, vdi): 

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

2364 complete due to external changes, namely vdi_delete and vdi_snapshot 

2365 that alter leaf-coalescibility of vdi""" 

2366 tracker = self.CoalesceTracker(self) 

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

2368 prevSizeVHD = vdi.getSizeVHD() 

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

2370 return False 

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

2372 tracker.printReasoning() 

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

2374 .format(uuid=vdi.uuid)) 

2375 return self._liveLeafCoalesce(vdi) 

2376 

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

2378 speed = None 

2379 total_time = endTime - startTime 

2380 if total_time > 0: 

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

2382 return speed 

2383 

2384 def writeSpeedToFile(self, speed): 

2385 content = [] 

2386 speedFile = None 

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

2388 self.lock() 

2389 try: 

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

2391 lines = "" 

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

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

2394 else: 

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

2396 content = speedFile.readlines() 

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

2398 if len(content) > N_RUNNING_AVERAGE: 

2399 del content[0] 

2400 lines = "".join(content) 

2401 

2402 util.atomicFileWrite(path, VAR_RUN, lines) 

2403 finally: 

2404 if speedFile is not None: 

2405 speedFile.close() 

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

2407 self.unlock() 

2408 

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

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

2411 if speed is None: 

2412 return 

2413 

2414 self.writeSpeedToFile(speed) 

2415 

2416 def getStorageSpeed(self): 

2417 speedFile = None 

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

2419 self.lock() 

2420 try: 

2421 speed = None 

2422 if os.path.isfile(path): 

2423 speedFile = open(path) 

2424 content = speedFile.readlines() 

2425 try: 

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

2427 except ValueError: 

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

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

2430 return speed 

2431 

2432 if len(content): 

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

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

2435 # Defensive, should be impossible. 

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

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

2438 speed = None 

2439 else: 

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

2441 format(uuid=self.uuid)) 

2442 else: 

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

2444 format(uuid=self.uuid)) 

2445 return speed 

2446 finally: 

2447 if not (speedFile is None): 

2448 speedFile.close() 

2449 self.unlock() 

2450 

2451 def _snapshotCoalesce(self, vdi): 

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

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

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

2455 assert(AUTO_ONLINE_LEAF_COALESCE_ENABLED) 

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

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

2458 try: 

2459 ret = self.xapi.singleSnapshotVDI(vdi) 

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

2461 except XenAPI.Failure as e: 

2462 if util.isInvalidVDI(e): 

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

2464 return False 

2465 raise 

2466 self.scanLocked() 

2467 tempSnap = vdi.parent 

2468 if not tempSnap.isCoalesceable(): 

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

2470 return False 

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

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

2473 vhdSize = vdi.getSizeVHD() 

2474 self._coalesce(tempSnap) 

2475 if not vdi.isLeafCoalesceable(): 

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

2477 return False 

2478 return True 

2479 

2480 def _liveLeafCoalesce(self, vdi) -> bool: 

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

2482 self.lock() 

2483 try: 

2484 self.scan() 

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

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

2487 return False 

2488 if not vdi.isLeafCoalesceable(): 

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

2490 return False 

2491 

2492 uuid = vdi.uuid 

2493 vdi.pause(failfast=True) 

2494 try: 

2495 try: 

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

2497 self._doCoalesceLeaf(vdi) 

2498 except: 

2499 Util.logException("_doCoalesceLeaf") 

2500 self._handleInterruptedCoalesceLeaf() 

2501 raise 

2502 finally: 

2503 vdi = self.getVDI(uuid) 

2504 if vdi: 

2505 vdi.ensureUnpaused() 

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

2507 if vdiOld: 

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

2509 self.deleteVDI(vdiOld) 

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

2511 finally: 

2512 self.cleanup() 

2513 self.unlock() 

2514 self.logFilter.logState() 

2515 return True 

2516 

2517 def _doCoalesceLeaf(self, vdi): 

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

2519 offline/atomic context""" 

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

2521 self._prepareCoalesceLeaf(vdi) 

2522 vdi.parent._setHidden(False) 

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

2524 vdi.validate(True) 

2525 vdi.parent.validate(True) 

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

2527 timeout = vdi.LIVE_LEAF_COALESCE_TIMEOUT 

2528 if vdi.getConfig(vdi.DB_LEAFCLSC) == vdi.LEAFCLSC_FORCE: 

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

2530 timeout = 0 

2531 vdi._coalesceVHD(timeout) 

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

2533 vdi.parent.validate(True) 

2534 #vdi._verifyContents(timeout / 2) 

2535 

2536 # rename 

2537 vdiUuid = vdi.uuid 

2538 oldName = vdi.fileName 

2539 origParentUuid = vdi.parent.uuid 

2540 vdi.rename(self.TMP_RENAME_PREFIX + vdiUuid) 

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

2542 vdi.parent.rename(vdiUuid) 

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

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

2545 

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

2547 # garbage 

2548 

2549 # update the VDI record 

2550 vdi.parent.delConfig(VDI.DB_VHD_PARENT) 

2551 if vdi.parent.raw: 

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

2553 vdi.parent.delConfig(VDI.DB_VHD_BLOCKS) 

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

2555 

2556 self._updateNode(vdi) 

2557 

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

2559 # minimize free space requirements) 

2560 parent = vdi.parent 

2561 vdi._setHidden(True) 

2562 vdi.parent.children = [] 

2563 vdi.parent = None 

2564 

2565 extraSpace = self._calcExtraSpaceNeeded(vdi, parent) 

2566 freeSpace = self.getFreeSpace() 

2567 if freeSpace < extraSpace: 

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

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

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

2571 self.deleteVDI(vdi) 

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

2573 

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

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

2576 

2577 self.forgetVDI(origParentUuid) 

2578 self._finishCoalesceLeaf(parent) 

2579 self._updateSlavesOnResize(parent) 

2580 

2581 def _calcExtraSpaceNeeded(self, child, parent) -> int: 

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

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

2584 if extra < 0: 

2585 extra = 0 

2586 return extra 

2587 

2588 def _prepareCoalesceLeaf(self, vdi) -> None: 

2589 pass 

2590 

2591 def _updateNode(self, vdi) -> None: 

2592 pass 

2593 

2594 def _finishCoalesceLeaf(self, parent) -> None: 

2595 pass 

2596 

2597 def _updateSlavesOnUndoLeafCoalesce(self, parent, child) -> None: 

2598 pass 

2599 

2600 def _updateSlavesOnRename(self, vdi, oldName, origParentUuid) -> None: 

2601 pass 

2602 

2603 def _updateSlavesOnResize(self, vdi) -> None: 

2604 pass 

2605 

2606 def _removeStaleVDIs(self, uuidsPresent) -> None: 

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

2608 if not uuid in uuidsPresent: 

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

2610 self.vdis[uuid]) 

2611 del self.vdis[uuid] 

2612 

2613 def _handleInterruptedCoalesceLeaf(self) -> None: 

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

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

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

2617 finish the operation""" 

2618 pass 

2619 

2620 def _buildTree(self, force): 

2621 self.vdiTrees = [] 

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

2623 if vdi.parentUuid: 

2624 parent = self.getVDI(vdi.parentUuid) 

2625 if not parent: 

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

2627 self.vdiTrees.append(vdi) 

2628 continue 

2629 if force: 

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

2631 (vdi.parentUuid, vdi.uuid)) 

2632 self.vdiTrees.append(vdi) 

2633 continue 

2634 else: 

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

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

2637 vdi.parent = parent 

2638 parent.children.append(vdi) 

2639 else: 

2640 self.vdiTrees.append(vdi) 

2641 

2642 

2643class FileSR(SR): 

2644 TYPE = SR.TYPE_FILE 

2645 CACHE_FILE_EXT = ".vhdcache" 

2646 # cache cleanup actions 

2647 CACHE_ACTION_KEEP = 0 

2648 CACHE_ACTION_REMOVE = 1 

2649 CACHE_ACTION_REMOVE_IF_INACTIVE = 2 

2650 

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

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

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

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

2655 

2656 @override 

2657 def scan(self, force=False) -> None: 

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

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

2660 vhds = self._scan(force) 

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

2662 vdi = self.getVDI(uuid) 

2663 if not vdi: 

2664 self.logFilter.logNewVDI(uuid) 

2665 vdi = FileVDI(self, uuid, False) 

2666 self.vdis[uuid] = vdi 

2667 vdi.load(vhdInfo) 

2668 uuidsPresent = list(vhds.keys()) 

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

2670 for rawName in rawList: 

2671 uuid = FileVDI.extractUuid(rawName) 

2672 uuidsPresent.append(uuid) 

2673 vdi = self.getVDI(uuid) 

2674 if not vdi: 

2675 self.logFilter.logNewVDI(uuid) 

2676 vdi = FileVDI(self, uuid, True) 

2677 self.vdis[uuid] = vdi 

2678 self._removeStaleVDIs(uuidsPresent) 

2679 self._buildTree(force) 

2680 self.logFilter.logState() 

2681 self._handleInterruptedCoalesceLeaf() 

2682 

2683 @override 

2684 def getFreeSpace(self) -> int: 

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

2686 

2687 @override 

2688 def deleteVDIs(self, vdiList) -> None: 

2689 rootDeleted = False 

2690 for vdi in vdiList: 

2691 if not vdi.parent: 

2692 rootDeleted = True 

2693 break 

2694 SR.deleteVDIs(self, vdiList) 

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

2696 self.xapi.markCacheSRsDirty() 

2697 

2698 @override 

2699 def cleanupCache(self, maxAge=-1) -> int: 

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

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

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

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

2704 <maxAge> hours. 

2705 Return number of caches removed. 

2706 """ 

2707 numRemoved = 0 

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

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

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

2711 for cacheFile in cacheFiles: 

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

2713 action = self.CACHE_ACTION_KEEP 

2714 rec = self.xapi.getRecordVDI(uuid) 

2715 if not rec: 

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

2717 action = self.CACHE_ACTION_REMOVE 

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

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

2720 action = self.CACHE_ACTION_REMOVE 

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

2722 lastAccess = datetime.datetime.fromtimestamp( \ 

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

2724 if lastAccess < cutoff: 

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

2726 action = self.CACHE_ACTION_REMOVE_IF_INACTIVE 

2727 

2728 if action == self.CACHE_ACTION_KEEP: 

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

2730 continue 

2731 

2732 lockId = uuid 

2733 parentUuid = None 

2734 if rec and rec["managed"]: 

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

2736 if parentUuid: 

2737 lockId = parentUuid 

2738 

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

2740 cacheLock.acquire() 

2741 try: 

2742 if self._cleanupCache(uuid, action): 

2743 numRemoved += 1 

2744 finally: 

2745 cacheLock.release() 

2746 return numRemoved 

2747 

2748 def _cleanupCache(self, uuid, action): 

2749 assert(action != self.CACHE_ACTION_KEEP) 

2750 rec = self.xapi.getRecordVDI(uuid) 

2751 if rec and rec["allow_caching"]: 

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

2753 return False 

2754 

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

2756 tapdisk = blktap2.Tapdisk.find_by_path(fullPath) 

2757 if tapdisk: 

2758 if action == self.CACHE_ACTION_REMOVE_IF_INACTIVE: 

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

2760 return False 

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

2762 tapdisk.shutdown() 

2763 

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

2765 os.unlink(fullPath) 

2766 return True 

2767 

2768 def _isCacheFileName(self, name): 

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

2770 name.endswith(self.CACHE_FILE_EXT) 

2771 

2772 def _scan(self, force): 

2773 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2774 error = False 

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

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

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

2778 if vhdInfo.error: 

2779 error = True 

2780 break 

2781 if not error: 

2782 return vhds 

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

2784 if force: 

2785 return vhds 

2786 raise util.SMException("Scan error") 

2787 

2788 @override 

2789 def deleteVDI(self, vdi) -> None: 

2790 self._checkSlaves(vdi) 

2791 SR.deleteVDI(self, vdi) 

2792 

2793 def _checkSlaves(self, vdi): 

2794 onlineHosts = self.xapi.getOnlineHosts() 

2795 abortFlag = IPCFlag(self.uuid) 

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

2797 hostRef = pbdRecord["host"] 

2798 if hostRef == self.xapi._hostRef: 

2799 continue 

2800 if abortFlag.test(FLAG_TYPE_ABORT): 

2801 raise AbortException("Aborting due to signal") 

2802 try: 

2803 self._checkSlave(hostRef, vdi) 

2804 except util.CommandException: 

2805 if hostRef in onlineHosts: 

2806 raise 

2807 

2808 def _checkSlave(self, hostRef, vdi): 

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

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

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

2812 text = _host.call_plugin( * call) 

2813 

2814 @override 

2815 def _handleInterruptedCoalesceLeaf(self) -> None: 

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

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

2818 fileList = os.listdir(self.path) 

2819 childName = uuid + vhdutil.FILE_EXTN_VHD 

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

2821 parentName1 = parentUuid + vhdutil.FILE_EXTN_VHD 

2822 parentName2 = parentUuid + vhdutil.FILE_EXTN_RAW 

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

2824 if parentPresent or tmpChildName in fileList: 

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 if child.hidden: 

2859 child._setHidden(False) 

2860 if not parent.hidden: 

2861 parent._setHidden(True) 

2862 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

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

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

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

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

2867 

2868 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

2870 vdi = self.getVDI(childUuid) 

2871 if not vdi: 

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

2873 try: 

2874 self.forgetVDI(parentUuid) 

2875 except XenAPI.Failure: 

2876 pass 

2877 self._updateSlavesOnResize(vdi) 

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

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

2880 

2881 

2882class LVHDSR(SR): 

2883 TYPE = SR.TYPE_LVHD 

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

2885 

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

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

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

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

2890 

2891 sr_ref = self.xapi.session.xenapi.SR.get_by_uuid(self.uuid) 

2892 other_conf = self.xapi.session.xenapi.SR.get_other_config(sr_ref) 

2893 lvm_conf = other_conf.get('lvm-conf') if other_conf else None 

2894 self.lvmCache = lvmcache.LVMCache(self.vgName, lvm_conf) 

2895 

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

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

2898 

2899 @override 

2900 def deleteVDI(self, vdi) -> None: 

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

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

2903 self._checkSlaves(vdi) 

2904 SR.deleteVDI(self, vdi) 

2905 

2906 @override 

2907 def forgetVDI(self, vdiUuid) -> None: 

2908 SR.forgetVDI(self, vdiUuid) 

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

2910 LVMMetadataHandler(mdpath).deleteVdiFromMetadata(vdiUuid) 

2911 

2912 @override 

2913 def getFreeSpace(self) -> int: 

2914 stats = lvutil._getVGstats(self.vgName) 

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

2916 

2917 @override 

2918 def cleanup(self): 

2919 if not self.lvActivator.deactivateAll(): 

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

2921 

2922 @override 

2923 def needUpdateBlockInfo(self) -> bool: 

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

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

2926 continue 

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

2928 return True 

2929 return False 

2930 

2931 @override 

2932 def updateBlockInfo(self) -> None: 

2933 numUpdated = 0 

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

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

2936 continue 

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

2938 vdi.updateBlockInfo() 

2939 numUpdated += 1 

2940 if numUpdated: 

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

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

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

2944 # inherit the refcount value and preventing the correct decrement 

2945 self.cleanup() 

2946 

2947 @override 

2948 def scan(self, force=False) -> None: 

2949 vdis = self._scan(force) 

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

2951 vdi = self.getVDI(uuid) 

2952 if not vdi: 

2953 self.logFilter.logNewVDI(uuid) 

2954 vdi = LVHDVDI(self, uuid, 

2955 vdiInfo.vdiType == vhdutil.VDI_TYPE_RAW) 

2956 self.vdis[uuid] = vdi 

2957 vdi.load(vdiInfo) 

2958 self._removeStaleVDIs(vdis.keys()) 

2959 self._buildTree(force) 

2960 self.logFilter.logState() 

2961 self._handleInterruptedCoalesceLeaf() 

2962 

2963 def _scan(self, force): 

2964 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2965 error = False 

2966 self.lvmCache.refresh() 

2967 vdis = lvhdutil.getVDIInfo(self.lvmCache) 

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

2969 if vdiInfo.scanError: 

2970 error = True 

2971 break 

2972 if not error: 

2973 return vdis 

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

2975 if force: 

2976 return vdis 

2977 raise util.SMException("Scan error") 

2978 

2979 @override 

2980 def _removeStaleVDIs(self, uuidsPresent) -> None: 

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

2982 if not uuid in uuidsPresent: 

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

2984 self.vdis[uuid]) 

2985 del self.vdis[uuid] 

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

2987 self.lvActivator.remove(uuid, False) 

2988 

2989 @override 

2990 def _liveLeafCoalesce(self, vdi) -> bool: 

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

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

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

2994 to avoid a protracted downtime""" 

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

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

2997 vdi.parent._increaseSizeVirt(vdi.sizeVirt) 

2998 

2999 return SR._liveLeafCoalesce(self, vdi) 

3000 

3001 @override 

3002 def _prepareCoalesceLeaf(self, vdi) -> None: 

3003 vdi._activateChain() 

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

3005 vdi.deflate() 

3006 vdi.inflateParentForCoalesce() 

3007 

3008 @override 

3009 def _updateNode(self, vdi) -> None: 

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

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

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

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

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

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

3016 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

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

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

3019 pCnt = pCnt - cBcnt 

3020 assert(pCnt >= 0) 

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

3022 

3023 @override 

3024 def _finishCoalesceLeaf(self, parent) -> None: 

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

3026 parent.inflateFully() 

3027 else: 

3028 parent.deflate() 

3029 

3030 @override 

3031 def _calcExtraSpaceNeeded(self, child, parent) -> int: 

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

3033 

3034 @override 

3035 def _handleInterruptedCoalesceLeaf(self) -> None: 

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

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

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

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

3040 self.TMP_RENAME_PREFIX + uuid 

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

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

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

3044 self.lvmCache.checkLV(parentLV2)) 

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

3046 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

3047 else: 

3048 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

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

3050 vdi = self.getVDI(uuid) 

3051 if vdi: 

3052 vdi.ensureUnpaused() 

3053 

3054 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

3056 parent = self.getVDI(parentUuid) 

3057 if not parent: 

3058 parent = self.getVDI(childUuid) 

3059 if not parent: 

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

3061 (parentUuid, childUuid)) 

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

3063 parent.rename(parentUuid) 

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

3065 

3066 child = self.getVDI(childUuid) 

3067 if not child: 

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

3069 if not child: 

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

3071 (childUuid, self.TMP_RENAME_PREFIX + childUuid)) 

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

3073 child.rename(childUuid) 

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

3075 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

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

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

3078 

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

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

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

3082 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

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

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

3085 pCnt = pCnt + cBcnt 

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

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

3088 

3089 parent.deflate() 

3090 child.inflateFully() 

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

3092 if child.hidden: 

3093 child._setHidden(False) 

3094 if not parent.hidden: 

3095 parent._setHidden(True) 

3096 if not parent.lvReadonly: 

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

3098 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

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

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

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

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

3103 

3104 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

3106 vdi = self.getVDI(childUuid) 

3107 if not vdi: 

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

3109 vdi.inflateFully() 

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

3111 try: 

3112 self.forgetVDI(parentUuid) 

3113 except XenAPI.Failure: 

3114 pass 

3115 self._updateSlavesOnResize(vdi) 

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

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

3118 

3119 def _checkSlaves(self, vdi): 

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

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

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

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

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

3125 "action1": "deactivateNoRefcount", 

3126 "lvName1": vdi.fileName, 

3127 "action2": "cleanupLockAndRefcount", 

3128 "uuid2": vdi.uuid, 

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

3130 onlineHosts = self.xapi.getOnlineHosts() 

3131 abortFlag = IPCFlag(self.uuid) 

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

3133 hostRef = pbdRecord["host"] 

3134 if hostRef == self.xapi._hostRef: 

3135 continue 

3136 if abortFlag.test(FLAG_TYPE_ABORT): 

3137 raise AbortException("Aborting due to signal") 

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

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

3140 try: 

3141 self.xapi.ensureInactive(hostRef, args) 

3142 except XenAPI.Failure: 

3143 if hostRef in onlineHosts: 

3144 raise 

3145 

3146 @override 

3147 def _updateSlavesOnUndoLeafCoalesce(self, parent, child) -> None: 

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

3149 if not slaves: 

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

3151 child) 

3152 return 

3153 

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

3155 self.TMP_RENAME_PREFIX + child.uuid 

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

3157 "action1": "deactivateNoRefcount", 

3158 "lvName1": tmpName, 

3159 "action2": "deactivateNoRefcount", 

3160 "lvName2": child.fileName, 

3161 "action3": "refresh", 

3162 "lvName3": child.fileName, 

3163 "action4": "refresh", 

3164 "lvName4": parent.fileName} 

3165 for slave in slaves: 

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

3167 (tmpName, child.fileName, parent.fileName, 

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

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

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

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

3172 

3173 @override 

3174 def _updateSlavesOnRename(self, vdi, oldNameLV, origParentUuid) -> None: 

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

3176 if not slaves: 

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

3178 return 

3179 

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

3181 "action1": "deactivateNoRefcount", 

3182 "lvName1": oldNameLV, 

3183 "action2": "refresh", 

3184 "lvName2": vdi.fileName, 

3185 "action3": "cleanupLockAndRefcount", 

3186 "uuid3": origParentUuid, 

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

3188 for slave in slaves: 

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

3190 (oldNameLV, vdi.fileName, 

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

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

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

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

3195 

3196 @override 

3197 def _updateSlavesOnResize(self, vdi) -> None: 

3198 uuids = [x.uuid for x in vdi.getAllLeaves()] 

3199 slaves = util.get_slaves_attached_on(self.xapi.session, uuids) 

3200 if not slaves: 

3201 util.SMlog("Update-on-resize: %s not attached on any slave" % vdi) 

3202 return 

3203 lvhdutil.lvRefreshOnSlaves(self.xapi.session, self.uuid, self.vgName, 

3204 vdi.fileName, vdi.uuid, slaves) 

3205 

3206 

3207class LinstorSR(SR): 

3208 TYPE = SR.TYPE_LINSTOR 

3209 

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

3211 if not LINSTOR_AVAILABLE: 

3212 raise util.SMException( 

3213 'Can\'t load cleanup LinstorSR: LINSTOR libraries are missing' 

3214 ) 

3215 

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

3217 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

3218 self._reloadLinstor() 

3219 

3220 @override 

3221 def deleteVDI(self, vdi) -> None: 

3222 self._checkSlaves(vdi) 

3223 SR.deleteVDI(self, vdi) 

3224 

3225 @override 

3226 def getFreeSpace(self) -> int: 

3227 return self._linstor.max_volume_size_allowed 

3228 

3229 @override 

3230 def scan(self, force=False) -> None: 

3231 all_vdi_info = self._scan(force) 

3232 for uuid, vdiInfo in all_vdi_info.items(): 

3233 # When vdiInfo is None, the VDI is RAW. 

3234 vdi = self.getVDI(uuid) 

3235 if not vdi: 

3236 self.logFilter.logNewVDI(uuid) 

3237 vdi = LinstorVDI(self, uuid, not vdiInfo) 

3238 self.vdis[uuid] = vdi 

3239 if vdiInfo: 

3240 vdi.load(vdiInfo) 

3241 self._removeStaleVDIs(all_vdi_info.keys()) 

3242 self._buildTree(force) 

3243 self.logFilter.logState() 

3244 self._handleInterruptedCoalesceLeaf() 

3245 

3246 @override 

3247 def pauseVDIs(self, vdiList) -> None: 

3248 self._linstor.ensure_volume_list_is_not_locked( 

3249 vdiList, timeout=LinstorVDI.VOLUME_LOCK_TIMEOUT 

3250 ) 

3251 return super(LinstorSR, self).pauseVDIs(vdiList) 

3252 

3253 def _reloadLinstor(self): 

3254 session = self.xapi.session 

3255 host_ref = util.get_this_host_ref(session) 

3256 sr_ref = session.xenapi.SR.get_by_uuid(self.uuid) 

3257 

3258 pbd = util.find_my_pbd(session, host_ref, sr_ref) 

3259 if pbd is None: 

3260 raise util.SMException('Failed to find PBD') 

3261 

3262 dconf = session.xenapi.PBD.get_device_config(pbd) 

3263 group_name = dconf['group-name'] 

3264 

3265 controller_uri = get_controller_uri() 

3266 self.journaler = LinstorJournaler( 

3267 controller_uri, group_name, logger=util.SMlog 

3268 ) 

3269 

3270 self._linstor = LinstorVolumeManager( 

3271 controller_uri, 

3272 group_name, 

3273 repair=True, 

3274 logger=util.SMlog 

3275 ) 

3276 self._vhdutil = LinstorVhdUtil(session, self._linstor) 

3277 

3278 def _scan(self, force): 

3279 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

3280 self._reloadLinstor() 

3281 error = False 

3282 try: 

3283 all_vdi_info = self._load_vdi_info() 

3284 for uuid, vdiInfo in all_vdi_info.items(): 

3285 if vdiInfo and vdiInfo.error: 

3286 error = True 

3287 break 

3288 if not error: 

3289 return all_vdi_info 

3290 Util.log('Scan error, retrying ({})'.format(i)) 

3291 except Exception as e: 

3292 Util.log('Scan exception, retrying ({}): {}'.format(i, e)) 

3293 Util.log(traceback.format_exc()) 

3294 

3295 if force: 

3296 return all_vdi_info 

3297 raise util.SMException('Scan error') 

3298 

3299 def _load_vdi_info(self): 

3300 all_vdi_info = {} 

3301 

3302 # TODO: Ensure metadata contains the right info. 

3303 

3304 all_volume_info = self._linstor.get_volumes_with_info() 

3305 volumes_metadata = self._linstor.get_volumes_with_metadata() 

3306 for vdi_uuid, volume_info in all_volume_info.items(): 

3307 try: 

3308 volume_metadata = volumes_metadata[vdi_uuid] 

3309 if not volume_info.name and not list(volume_metadata.items()): 

3310 continue # Ignore it, probably deleted. 

3311 

3312 if vdi_uuid.startswith('DELETED_'): 

3313 # Assume it's really a RAW volume of a failed snap without VHD header/footer. 

3314 # We must remove this VDI now without adding it in the VDI list. 

3315 # Otherwise `Relinking` calls and other actions can be launched on it. 

3316 # We don't want that... 

3317 Util.log('Deleting bad VDI {}'.format(vdi_uuid)) 

3318 

3319 self.lock() 

3320 try: 

3321 self._linstor.destroy_volume(vdi_uuid) 

3322 try: 

3323 self.forgetVDI(vdi_uuid) 

3324 except: 

3325 pass 

3326 except Exception as e: 

3327 Util.log('Cannot delete bad VDI: {}'.format(e)) 

3328 finally: 

3329 self.unlock() 

3330 continue 

3331 

3332 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

3333 volume_name = self._linstor.get_volume_name(vdi_uuid) 

3334 if volume_name.startswith(LINSTOR_PERSISTENT_PREFIX): 

3335 # Always RAW! 

3336 info = None 

3337 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

3338 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3339 else: 

3340 # Ensure it's not a VHD... 

3341 try: 

3342 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3343 except: 

3344 try: 

3345 self._vhdutil.force_repair( 

3346 self._linstor.get_device_path(vdi_uuid) 

3347 ) 

3348 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3349 except: 

3350 info = None 

3351 

3352 except Exception as e: 

3353 Util.log( 

3354 ' [VDI {}: failed to load VDI info]: {}' 

3355 .format(vdi_uuid, e) 

3356 ) 

3357 info = vhdutil.VHDInfo(vdi_uuid) 

3358 info.error = 1 

3359 

3360 all_vdi_info[vdi_uuid] = info 

3361 

3362 return all_vdi_info 

3363 

3364 @override 

3365 def _prepareCoalesceLeaf(self, vdi) -> None: 

3366 vdi._activateChain() 

3367 vdi.deflate() 

3368 vdi._inflateParentForCoalesce() 

3369 

3370 @override 

3371 def _finishCoalesceLeaf(self, parent) -> None: 

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

3373 parent.inflateFully() 

3374 else: 

3375 parent.deflate() 

3376 

3377 @override 

3378 def _calcExtraSpaceNeeded(self, child, parent) -> int: 

3379 return LinstorVhdUtil.compute_volume_size(parent.sizeVirt, parent.vdi_type) - parent.getDrbdSize() 

3380 

3381 def _hasValidDevicePath(self, uuid): 

3382 try: 

3383 self._linstor.get_device_path(uuid) 

3384 except Exception: 

3385 # TODO: Maybe log exception. 

3386 return False 

3387 return True 

3388 

3389 @override 

3390 def _liveLeafCoalesce(self, vdi) -> bool: 

3391 self.lock() 

3392 try: 

3393 self._linstor.ensure_volume_is_not_locked( 

3394 vdi.uuid, timeout=LinstorVDI.VOLUME_LOCK_TIMEOUT 

3395 ) 

3396 return super(LinstorSR, self)._liveLeafCoalesce(vdi) 

3397 finally: 

3398 self.unlock() 

3399 

3400 @override 

3401 def _handleInterruptedCoalesceLeaf(self) -> None: 

3402 entries = self.journaler.get_all(VDI.JRN_LEAF) 

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

3404 if self._hasValidDevicePath(parentUuid) or \ 

3405 self._hasValidDevicePath(self.TMP_RENAME_PREFIX + uuid): 

3406 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

3407 else: 

3408 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

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

3410 vdi = self.getVDI(uuid) 

3411 if vdi: 

3412 vdi.ensureUnpaused() 

3413 

3414 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

3415 Util.log('*** UNDO LEAF-COALESCE') 

3416 parent = self.getVDI(parentUuid) 

3417 if not parent: 

3418 parent = self.getVDI(childUuid) 

3419 if not parent: 

3420 raise util.SMException( 

3421 'Neither {} nor {} found'.format(parentUuid, childUuid) 

3422 ) 

3423 Util.log( 

3424 'Renaming parent back: {} -> {}'.format(childUuid, parentUuid) 

3425 ) 

3426 parent.rename(parentUuid) 

3427 

3428 child = self.getVDI(childUuid) 

3429 if not child: 

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

3431 if not child: 

3432 raise util.SMException( 

3433 'Neither {} nor {} found'.format( 

3434 childUuid, self.TMP_RENAME_PREFIX + childUuid 

3435 ) 

3436 ) 

3437 Util.log('Renaming child back to {}'.format(childUuid)) 

3438 child.rename(childUuid) 

3439 Util.log('Updating the VDI record') 

3440 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

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

3442 

3443 # TODO: Maybe deflate here. 

3444 

3445 if child.hidden: 

3446 child._setHidden(False) 

3447 if not parent.hidden: 

3448 parent._setHidden(True) 

3449 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

3450 Util.log('*** leaf-coalesce undo successful') 

3451 

3452 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

3453 Util.log('*** FINISH LEAF-COALESCE') 

3454 vdi = self.getVDI(childUuid) 

3455 if not vdi: 

3456 raise util.SMException('VDI {} not found'.format(childUuid)) 

3457 # TODO: Maybe inflate. 

3458 try: 

3459 self.forgetVDI(parentUuid) 

3460 except XenAPI.Failure: 

3461 pass 

3462 self._updateSlavesOnResize(vdi) 

3463 Util.log('*** finished leaf-coalesce successfully') 

3464 

3465 def _checkSlaves(self, vdi): 

3466 try: 

3467 all_openers = self._linstor.get_volume_openers(vdi.uuid) 

3468 for openers in all_openers.values(): 

3469 for opener in openers.values(): 

3470 if opener['process-name'] != 'tapdisk': 

3471 raise util.SMException( 

3472 'VDI {} is in use: {}'.format(vdi.uuid, all_openers) 

3473 ) 

3474 except LinstorVolumeManagerError as e: 

3475 if e.code != LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

3476 raise 

3477 

3478 

3479################################################################################ 

3480# 

3481# Helpers 

3482# 

3483def daemonize(): 

3484 pid = os.fork() 

3485 if pid: 

3486 os.waitpid(pid, 0) 

3487 Util.log("New PID [%d]" % pid) 

3488 return False 

3489 os.chdir("/") 

3490 os.setsid() 

3491 pid = os.fork() 

3492 if pid: 

3493 Util.log("Will finish as PID [%d]" % pid) 

3494 os._exit(0) 

3495 for fd in [0, 1, 2]: 

3496 try: 

3497 os.close(fd) 

3498 except OSError: 

3499 pass 

3500 # we need to fill those special fd numbers or pread won't work 

3501 sys.stdin = open("/dev/null", 'r') 

3502 sys.stderr = open("/dev/null", 'w') 

3503 sys.stdout = open("/dev/null", 'w') 

3504 # As we're a new process we need to clear the lock objects 

3505 lock.Lock.clearAll() 

3506 return True 

3507 

3508 

3509def normalizeType(type): 

3510 if type in LVHDSR.SUBTYPES: 

3511 type = SR.TYPE_LVHD 

3512 if type in ["lvm", "lvmoiscsi", "lvmohba", "lvmofcoe"]: 

3513 # temporary while LVHD is symlinked as LVM 

3514 type = SR.TYPE_LVHD 

3515 if type in [ 

3516 "ext", "nfs", "ocfsoiscsi", "ocfsohba", "smb", "cephfs", "glusterfs", 

3517 "moosefs", "xfs", "zfs", "largeblock" 

3518 ]: 

3519 type = SR.TYPE_FILE 

3520 if type in ["linstor"]: 

3521 type = SR.TYPE_LINSTOR 

3522 if type not in SR.TYPES: 

3523 raise util.SMException("Unsupported SR type: %s" % type) 

3524 return type 

3525 

3526GCPAUSE_DEFAULT_SLEEP = 5 * 60 

3527 

3528 

3529def _gc_init_file(sr_uuid): 

3530 return os.path.join(NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init') 

3531 

3532 

3533def _create_init_file(sr_uuid): 

3534 util.makedirs(os.path.join(NON_PERSISTENT_DIR, str(sr_uuid))) 

3535 with open(os.path.join( 

3536 NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init'), 'w+') as f: 

3537 f.write('1') 

3538 

3539 

3540def _gcLoopPause(sr, dryRun=False, immediate=False): 

3541 if immediate: 

3542 return 

3543 

3544 # Check to see if the GCPAUSE_FISTPOINT is present. If so the fist 

3545 # point will just return. Otherwise, fall back on an abortable sleep. 

3546 

3547 if util.fistpoint.is_active(util.GCPAUSE_FISTPOINT): 

3548 

3549 util.fistpoint.activate_custom_fn(util.GCPAUSE_FISTPOINT, 3549 ↛ exitline 3549 didn't jump to the function exit

3550 lambda *args: None) 

3551 elif os.path.exists(_gc_init_file(sr.uuid)): 

3552 def abortTest(): 

3553 return IPCFlag(sr.uuid).test(FLAG_TYPE_ABORT) 

3554 

3555 # If time.sleep hangs we are in deep trouble, however for 

3556 # completeness we set the timeout of the abort thread to 

3557 # 110% of GCPAUSE_DEFAULT_SLEEP. 

3558 Util.log("GC active, about to go quiet") 

3559 Util.runAbortable(lambda: time.sleep(GCPAUSE_DEFAULT_SLEEP), 3559 ↛ exitline 3559 didn't run the lambda on line 3559

3560 None, sr.uuid, abortTest, VDI.POLL_INTERVAL, 

3561 GCPAUSE_DEFAULT_SLEEP * 1.1) 

3562 Util.log("GC active, quiet period ended") 

3563 

3564 

3565def _gcLoop(sr, dryRun=False, immediate=False): 

3566 if not lockGCActive.acquireNoblock(): 3566 ↛ 3567line 3566 didn't jump to line 3567, because the condition on line 3566 was never true

3567 Util.log("Another GC instance already active, exiting") 

3568 return 

3569 

3570 # Check we're still attached after acquiring locks 

3571 if not sr.xapi.isPluggedHere(): 

3572 Util.log("SR no longer attached, exiting") 

3573 return 

3574 

3575 # Clean up Intellicache files 

3576 sr.cleanupCache() 

3577 

3578 # Track how many we do 

3579 coalesced = 0 

3580 task_status = "success" 

3581 try: 

3582 # Check if any work needs to be done 

3583 if not sr.xapi.isPluggedHere(): 3583 ↛ 3584line 3583 didn't jump to line 3584, because the condition on line 3583 was never true

3584 Util.log("SR no longer attached, exiting") 

3585 return 

3586 sr.scanLocked() 

3587 if not sr.hasWork(): 

3588 Util.log("No work, exiting") 

3589 return 

3590 sr.xapi.create_task( 

3591 "Garbage Collection", 

3592 "Garbage collection for SR %s" % sr.uuid) 

3593 _gcLoopPause(sr, dryRun, immediate=immediate) 

3594 while True: 

3595 if not sr.xapi.isPluggedHere(): 3595 ↛ 3596line 3595 didn't jump to line 3596, because the condition on line 3595 was never true

3596 Util.log("SR no longer attached, exiting") 

3597 break 

3598 sr.scanLocked() 

3599 if not sr.hasWork(): 

3600 Util.log("No work, exiting") 

3601 break 

3602 

3603 if not lockGCRunning.acquireNoblock(): 3603 ↛ 3604line 3603 didn't jump to line 3604, because the condition on line 3603 was never true

3604 Util.log("Unable to acquire GC running lock.") 

3605 return 

3606 try: 

3607 if not sr.gcEnabled(): 3607 ↛ 3608line 3607 didn't jump to line 3608, because the condition on line 3607 was never true

3608 break 

3609 

3610 sr.xapi.update_task_progress("done", coalesced) 

3611 

3612 sr.cleanupCoalesceJournals() 

3613 # Create the init file here in case startup is waiting on it 

3614 _create_init_file(sr.uuid) 

3615 sr.scanLocked() 

3616 sr.updateBlockInfo() 

3617 

3618 howmany = len(sr.findGarbage()) 

3619 if howmany > 0: 

3620 Util.log("Found %d orphaned vdis" % howmany) 

3621 sr.lock() 

3622 try: 

3623 sr.garbageCollect(dryRun) 

3624 finally: 

3625 sr.unlock() 

3626 sr.xapi.srUpdate() 

3627 

3628 candidate = sr.findCoalesceable() 

3629 if candidate: 

3630 util.fistpoint.activate( 

3631 "LVHDRT_finding_a_suitable_pair", sr.uuid) 

3632 sr.coalesce(candidate, dryRun) 

3633 sr.xapi.srUpdate() 

3634 coalesced += 1 

3635 continue 

3636 

3637 candidate = sr.findLeafCoalesceable() 

3638 if candidate: 3638 ↛ 3645line 3638 didn't jump to line 3645, because the condition on line 3638 was never false

3639 sr.coalesceLeaf(candidate, dryRun) 

3640 sr.xapi.srUpdate() 

3641 coalesced += 1 

3642 continue 

3643 

3644 finally: 

3645 lockGCRunning.release() 3645 ↛ 3650line 3645 didn't jump to line 3650, because the break on line 3608 wasn't executed

3646 except: 

3647 task_status = "failure" 

3648 raise 

3649 finally: 

3650 sr.xapi.set_task_status(task_status) 

3651 Util.log("GC process exiting, no work left") 

3652 _create_init_file(sr.uuid) 

3653 lockGCActive.release() 

3654 

3655 

3656def _xapi_enabled(session, hostref): 

3657 host = session.xenapi.host.get_record(hostref) 

3658 return host['enabled'] 

3659 

3660 

3661def _ensure_xapi_initialised(session): 

3662 """ 

3663 Don't want to start GC until Xapi is fully initialised 

3664 """ 

3665 local_session = None 

3666 if session is None: 

3667 local_session = util.get_localAPI_session() 

3668 session = local_session 

3669 

3670 try: 

3671 hostref = session.xenapi.host.get_by_uuid(util.get_this_host()) 

3672 while not _xapi_enabled(session, hostref): 

3673 util.SMlog("Xapi not ready, GC waiting") 

3674 time.sleep(15) 

3675 finally: 

3676 if local_session is not None: 

3677 local_session.xenapi.session.logout() 

3678 

3679def _gc(session, srUuid, dryRun=False, immediate=False): 

3680 init(srUuid) 

3681 _ensure_xapi_initialised(session) 

3682 sr = SR.getInstance(srUuid, session) 

3683 if not sr.gcEnabled(False): 3683 ↛ 3684line 3683 didn't jump to line 3684, because the condition on line 3683 was never true

3684 return 

3685 

3686 try: 

3687 _gcLoop(sr, dryRun, immediate=immediate) 

3688 finally: 

3689 sr.cleanup() 

3690 sr.logFilter.logState() 

3691 del sr.xapi 

3692 

3693 

3694def _abort(srUuid, soft=False): 

3695 """Aborts an GC/coalesce. 

3696 

3697 srUuid: the UUID of the SR whose GC/coalesce must be aborted 

3698 soft: If set to True and there is a pending abort signal, the function 

3699 doesn't do anything. If set to False, a new abort signal is issued. 

3700 

3701 returns: If soft is set to False, we return True holding lockGCActive. If 

3702 soft is set to False and an abort signal is pending, we return False 

3703 without holding lockGCActive. An exception is raised in case of error.""" 

3704 Util.log("=== SR %s: abort ===" % (srUuid)) 

3705 init(srUuid) 

3706 if not lockGCActive.acquireNoblock(): 

3707 gotLock = False 

3708 Util.log("Aborting currently-running instance (SR %s)" % srUuid) 

3709 abortFlag = IPCFlag(srUuid) 

3710 if not abortFlag.set(FLAG_TYPE_ABORT, soft): 

3711 return False 

3712 for i in range(SR.LOCK_RETRY_ATTEMPTS): 

3713 gotLock = lockGCActive.acquireNoblock() 

3714 if gotLock: 

3715 break 

3716 time.sleep(SR.LOCK_RETRY_INTERVAL) 

3717 abortFlag.clear(FLAG_TYPE_ABORT) 

3718 if not gotLock: 

3719 raise util.CommandException(code=errno.ETIMEDOUT, 

3720 reason="SR %s: error aborting existing process" % srUuid) 

3721 return True 

3722 

3723 

3724def init(srUuid): 

3725 global lockGCRunning 

3726 if not lockGCRunning: 3726 ↛ 3727line 3726 didn't jump to line 3727, because the condition on line 3726 was never true

3727 lockGCRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, srUuid) 

3728 global lockGCActive 

3729 if not lockGCActive: 3729 ↛ 3730line 3729 didn't jump to line 3730, because the condition on line 3729 was never true

3730 lockGCActive = LockActive(srUuid) 

3731 

3732 

3733class LockActive: 

3734 """ 

3735 Wraps the use of LOCK_TYPE_GC_ACTIVE such that the lock cannot be acquired 

3736 if another process holds the SR lock. 

3737 """ 

3738 def __init__(self, srUuid): 

3739 self._lock = lock.Lock(LOCK_TYPE_GC_ACTIVE, srUuid) 

3740 self._srLock = lock.Lock(vhdutil.LOCK_TYPE_SR, srUuid) 

3741 

3742 def acquireNoblock(self): 

3743 self._srLock.acquire() 

3744 

3745 try: 

3746 return self._lock.acquireNoblock() 

3747 finally: 

3748 self._srLock.release() 

3749 

3750 def release(self): 

3751 self._lock.release() 

3752 

3753 

3754def usage(): 

3755 output = """Garbage collect and/or coalesce VHDs in a VHD-based SR 

3756 

3757Parameters: 

3758 -u --uuid UUID SR UUID 

3759 and one of: 

3760 -g --gc garbage collect, coalesce, and repeat while there is work 

3761 -G --gc_force garbage collect once, aborting any current operations 

3762 -c --cache-clean <max_age> clean up IntelliCache cache files older than 

3763 max_age hours 

3764 -a --abort abort any currently running operation (GC or coalesce) 

3765 -q --query query the current state (GC'ing, coalescing or not running) 

3766 -x --disable disable GC/coalesce (will be in effect until you exit) 

3767 -t --debug see Debug below 

3768 

3769Options: 

3770 -b --background run in background (return immediately) (valid for -g only) 

3771 -f --force continue in the presence of VHDs with errors (when doing 

3772 GC, this might cause removal of any such VHDs) (only valid 

3773 for -G) (DANGEROUS) 

3774 

3775Debug: 

3776 The --debug parameter enables manipulation of LVHD VDIs for debugging 

3777 purposes. ** NEVER USE IT ON A LIVE VM ** 

3778 The following parameters are required: 

3779 -t --debug <cmd> <cmd> is one of "activate", "deactivate", "inflate", 

3780 "deflate". 

3781 -v --vdi_uuid VDI UUID 

3782 """ 

3783 #-d --dry-run don't actually perform any SR-modifying operations 

3784 print(output) 

3785 Util.log("(Invalid usage)") 

3786 sys.exit(1) 

3787 

3788 

3789############################################################################## 

3790# 

3791# API 

3792# 

3793def abort(srUuid, soft=False): 

3794 """Abort GC/coalesce if we are currently GC'ing or coalescing a VDI pair. 

3795 """ 

3796 if _abort(srUuid, soft): 

3797 Util.log("abort: releasing the process lock") 

3798 lockGCActive.release() 

3799 return True 

3800 else: 

3801 return False 

3802 

3803 

3804def gc(session, srUuid, inBackground, dryRun=False): 

3805 """Garbage collect all deleted VDIs in SR "srUuid". Fork & return 

3806 immediately if inBackground=True. 

3807 

3808 The following algorithm is used: 

3809 1. If we are already GC'ing in this SR, return 

3810 2. If we are already coalescing a VDI pair: 

3811 a. Scan the SR and determine if the VDI pair is GC'able 

3812 b. If the pair is not GC'able, return 

3813 c. If the pair is GC'able, abort coalesce 

3814 3. Scan the SR 

3815 4. If there is nothing to collect, nor to coalesce, return 

3816 5. If there is something to collect, GC all, then goto 3 

3817 6. If there is something to coalesce, coalesce one pair, then goto 3 

3818 """ 

3819 Util.log("=== SR %s: gc ===" % srUuid) 

3820 if inBackground: 

3821 if daemonize(): 3821 ↛ exitline 3821 didn't return from function 'gc', because the condition on line 3821 was never false

3822 # we are now running in the background. Catch & log any errors 

3823 # because there is no other way to propagate them back at this 

3824 # point 

3825 

3826 try: 

3827 _gc(None, srUuid, dryRun) 

3828 except AbortException: 

3829 Util.log("Aborted") 

3830 except Exception: 

3831 Util.logException("gc") 

3832 Util.log("* * * * * SR %s: ERROR\n" % srUuid) 

3833 os._exit(0) 

3834 else: 

3835 _gc(session, srUuid, dryRun, immediate=True) 

3836 

3837 

3838def start_gc(session, sr_uuid): 

3839 """ 

3840 This function is used to try to start a backgrounded GC session by forking 

3841 the current process. If using the systemd version, call start_gc_service() instead. 

3842 """ 

3843 # don't bother if an instance already running (this is just an 

3844 # optimization to reduce the overhead of forking a new process if we 

3845 # don't have to, but the process will check the lock anyways) 

3846 lockRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, sr_uuid) 

3847 if not lockRunning.acquireNoblock(): 

3848 if should_preempt(session, sr_uuid): 

3849 util.SMlog("Aborting currently-running coalesce of garbage VDI") 

3850 try: 

3851 if not abort(sr_uuid, soft=True): 

3852 util.SMlog("The GC has already been scheduled to re-start") 

3853 except util.CommandException as e: 

3854 if e.code != errno.ETIMEDOUT: 

3855 raise 

3856 util.SMlog('failed to abort the GC') 

3857 else: 

3858 util.SMlog("A GC instance already running, not kicking") 

3859 return 

3860 else: 

3861 lockRunning.release() 

3862 

3863 util.SMlog(f"Starting GC file is {__file__}") 

3864 subprocess.run([__file__, '-b', '-u', sr_uuid, '-g'], 

3865 stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3866 

3867def start_gc_service(sr_uuid, wait=False): 

3868 """ 

3869 This starts the templated systemd service which runs GC on the given SR UUID. 

3870 If the service was already started, this is a no-op. 

3871 

3872 Because the service is a one-shot with RemainAfterExit=no, when called with 

3873 wait=True this will run the service synchronously and will not return until the 

3874 run has finished. This is used to force a run of the GC instead of just kicking it 

3875 in the background. 

3876 """ 

3877 sr_uuid_esc = sr_uuid.replace("-", "\\x2d") 

3878 util.SMlog(f"Kicking SMGC@{sr_uuid}...") 

3879 cmd=[ "/usr/bin/systemctl", "--quiet" ] 

3880 if not wait: 3880 ↛ 3882line 3880 didn't jump to line 3882, because the condition on line 3880 was never false

3881 cmd.append("--no-block") 

3882 cmd += ["start", f"SMGC@{sr_uuid_esc}"] 

3883 subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3884 

3885 

3886def gc_force(session, srUuid, force=False, dryRun=False, lockSR=False): 

3887 """Garbage collect all deleted VDIs in SR "srUuid". The caller must ensure 

3888 the SR lock is held. 

3889 The following algorithm is used: 

3890 1. If we are already GC'ing or coalescing a VDI pair, abort GC/coalesce 

3891 2. Scan the SR 

3892 3. GC 

3893 4. return 

3894 """ 

3895 Util.log("=== SR %s: gc_force ===" % srUuid) 

3896 init(srUuid) 

3897 sr = SR.getInstance(srUuid, session, lockSR, True) 

3898 if not lockGCActive.acquireNoblock(): 

3899 abort(srUuid) 

3900 else: 

3901 Util.log("Nothing was running, clear to proceed") 

3902 

3903 if force: 

3904 Util.log("FORCED: will continue even if there are VHD errors") 

3905 sr.scanLocked(force) 

3906 sr.cleanupCoalesceJournals() 

3907 

3908 try: 

3909 sr.cleanupCache() 

3910 sr.garbageCollect(dryRun) 

3911 finally: 

3912 sr.cleanup() 

3913 sr.logFilter.logState() 

3914 lockGCActive.release() 

3915 

3916 

3917def get_state(srUuid): 

3918 """Return whether GC/coalesce is currently running or not. This asks systemd for 

3919 the state of the templated SMGC service and will return True if it is "activating" 

3920 or "running" (for completeness, as in practice it will never achieve the latter state) 

3921 """ 

3922 sr_uuid_esc = srUuid.replace("-", "\\x2d") 

3923 cmd=[ "/usr/bin/systemctl", "is-active", f"SMGC@{sr_uuid_esc}"] 

3924 result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3925 state = result.stdout.decode('utf-8').rstrip() 

3926 if state == "activating" or state == "running": 

3927 return True 

3928 return False 

3929 

3930 

3931def should_preempt(session, srUuid): 

3932 sr = SR.getInstance(srUuid, session) 

3933 entries = sr.journaler.getAll(VDI.JRN_COALESCE) 

3934 if len(entries) == 0: 

3935 return False 

3936 elif len(entries) > 1: 

3937 raise util.SMException("More than one coalesce entry: " + str(entries)) 

3938 sr.scanLocked() 

3939 coalescedUuid = entries.popitem()[0] 

3940 garbage = sr.findGarbage() 

3941 for vdi in garbage: 

3942 if vdi.uuid == coalescedUuid: 

3943 return True 

3944 return False 

3945 

3946 

3947def get_coalesceable_leaves(session, srUuid, vdiUuids): 

3948 coalesceable = [] 

3949 sr = SR.getInstance(srUuid, session) 

3950 sr.scanLocked() 

3951 for uuid in vdiUuids: 

3952 vdi = sr.getVDI(uuid) 

3953 if not vdi: 

3954 raise util.SMException("VDI %s not found" % uuid) 

3955 if vdi.isLeafCoalesceable(): 

3956 coalesceable.append(uuid) 

3957 return coalesceable 

3958 

3959 

3960def cache_cleanup(session, srUuid, maxAge): 

3961 sr = SR.getInstance(srUuid, session) 

3962 return sr.cleanupCache(maxAge) 

3963 

3964 

3965def debug(sr_uuid, cmd, vdi_uuid): 

3966 Util.log("Debug command: %s" % cmd) 

3967 sr = SR.getInstance(sr_uuid, None) 

3968 if not isinstance(sr, LVHDSR): 

3969 print("Error: not an LVHD SR") 

3970 return 

3971 sr.scanLocked() 

3972 vdi = sr.getVDI(vdi_uuid) 

3973 if not vdi: 

3974 print("Error: VDI %s not found") 

3975 return 

3976 print("Running %s on SR %s" % (cmd, sr)) 

3977 print("VDI before: %s" % vdi) 

3978 if cmd == "activate": 

3979 vdi._activate() 

3980 print("VDI file: %s" % vdi.path) 

3981 if cmd == "deactivate": 

3982 ns = lvhdutil.NS_PREFIX_LVM + sr.uuid 

3983 sr.lvmCache.deactivate(ns, vdi.uuid, vdi.fileName, False) 

3984 if cmd == "inflate": 

3985 vdi.inflateFully() 

3986 sr.cleanup() 

3987 if cmd == "deflate": 

3988 vdi.deflate() 

3989 sr.cleanup() 

3990 sr.scanLocked() 

3991 print("VDI after: %s" % vdi) 

3992 

3993 

3994def abort_optional_reenable(uuid): 

3995 print("Disabling GC/coalesce for %s" % uuid) 

3996 ret = _abort(uuid) 

3997 input("Press enter to re-enable...") 

3998 print("GC/coalesce re-enabled") 

3999 lockGCRunning.release() 

4000 if ret: 

4001 lockGCActive.release() 

4002 

4003 

4004############################################################################## 

4005# 

4006# CLI 

4007# 

4008def main(): 

4009 action = "" 

4010 uuid = "" 

4011 background = False 

4012 force = False 

4013 dryRun = False 

4014 debug_cmd = "" 

4015 vdi_uuid = "" 

4016 shortArgs = "gGc:aqxu:bfdt:v:" 

4017 longArgs = ["gc", "gc_force", "clean_cache", "abort", "query", "disable", 

4018 "uuid=", "background", "force", "dry-run", "debug=", "vdi_uuid="] 

4019 

4020 try: 

4021 opts, args = getopt.getopt(sys.argv[1:], shortArgs, longArgs) 

4022 except getopt.GetoptError: 

4023 usage() 

4024 for o, a in opts: 

4025 if o in ("-g", "--gc"): 

4026 action = "gc" 

4027 if o in ("-G", "--gc_force"): 

4028 action = "gc_force" 

4029 if o in ("-c", "--clean_cache"): 

4030 action = "clean_cache" 

4031 maxAge = int(a) 

4032 if o in ("-a", "--abort"): 

4033 action = "abort" 

4034 if o in ("-q", "--query"): 

4035 action = "query" 

4036 if o in ("-x", "--disable"): 

4037 action = "disable" 

4038 if o in ("-u", "--uuid"): 

4039 uuid = a 

4040 if o in ("-b", "--background"): 

4041 background = True 

4042 if o in ("-f", "--force"): 

4043 force = True 

4044 if o in ("-d", "--dry-run"): 

4045 Util.log("Dry run mode") 

4046 dryRun = True 

4047 if o in ("-t", "--debug"): 

4048 action = "debug" 

4049 debug_cmd = a 

4050 if o in ("-v", "--vdi_uuid"): 

4051 vdi_uuid = a 

4052 

4053 if not action or not uuid: 

4054 usage() 

4055 if action == "debug" and not (debug_cmd and vdi_uuid) or \ 

4056 action != "debug" and (debug_cmd or vdi_uuid): 

4057 usage() 

4058 

4059 if action != "query" and action != "debug": 

4060 print("All output goes to log") 

4061 

4062 if action == "gc": 

4063 gc(None, uuid, background, dryRun) 

4064 elif action == "gc_force": 

4065 gc_force(None, uuid, force, dryRun, True) 

4066 elif action == "clean_cache": 

4067 cache_cleanup(None, uuid, maxAge) 

4068 elif action == "abort": 

4069 abort(uuid) 

4070 elif action == "query": 

4071 print("Currently running: %s" % get_state(uuid)) 

4072 elif action == "disable": 

4073 abort_optional_reenable(uuid) 

4074 elif action == "debug": 

4075 debug(uuid, debug_cmd, vdi_uuid) 

4076 

4077 

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

4079 main()