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 

21import os 

22import os.path 

23import sys 

24import time 

25import signal 

26import subprocess 

27import getopt 

28import datetime 

29import traceback 

30import base64 

31import zlib 

32import errno 

33import stat 

34 

35import XenAPI # pylint: disable=import-error 

36import util 

37import lvutil 

38import vhdutil 

39import lvhdutil 

40import lvmcache 

41import journaler 

42import fjournaler 

43import lock 

44import blktap2 

45import xs_errors 

46from refcounter import RefCounter 

47from ipc import IPCFlag 

48from lvmanager import LVActivator 

49from srmetadata import LVMMetadataHandler, VDI_TYPE_TAG 

50from functools import reduce 

51from time import monotonic as _time 

52 

53try: 

54 from linstorjournaler import LinstorJournaler 

55 from linstorvhdutil import LinstorVhdUtil 

56 from linstorvolumemanager import get_controller_uri 

57 from linstorvolumemanager import LinstorVolumeManager 

58 from linstorvolumemanager import LinstorVolumeManagerError 

59 from linstorvolumemanager import PERSISTENT_PREFIX as LINSTOR_PERSISTENT_PREFIX 

60 

61 LINSTOR_AVAILABLE = True 

62except ImportError: 

63 LINSTOR_AVAILABLE = False 

64 

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

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

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

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

69# the VM first) 

70AUTO_ONLINE_LEAF_COALESCE_ENABLED = True 

71 

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

73 

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

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

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

77lockGCRunning = None 

78 

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

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

81LOCK_TYPE_GC_ACTIVE = "gc_active" 

82lockGCActive = None 

83 

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

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

86DEFAULT_COALESCE_ERR_RATE = 1.0 / 60 

87 

88COALESCE_LAST_ERR_TAG = 'last-coalesce-error' 

89COALESCE_ERR_RATE_TAG = 'coalesce-error-rate' 

90VAR_RUN = "/var/run/" 

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

92 

93N_RUNNING_AVERAGE = 10 

94 

95NON_PERSISTENT_DIR = '/run/nonpersistent/sm' 

96 

97 

98class AbortException(util.SMException): 

99 pass 

100 

101 

102################################################################################ 

103# 

104# Util 

105# 

106class Util: 

107 RET_RC = 1 

108 RET_STDOUT = 2 

109 RET_STDERR = 4 

110 

111 UUID_LEN = 36 

112 

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

114 

115 def log(text): 

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

117 log = staticmethod(log) 

118 

119 def logException(tag): 

120 info = sys.exc_info() 

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

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

123 sys.exit(0) 

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

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

126 Util.log(" ***********************") 

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

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

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

130 Util.log(tb) 

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

132 logException = staticmethod(logException) 

133 

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

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

136 proc = subprocess.Popen(args, 

137 stdin=subprocess.PIPE, \ 

138 stdout=subprocess.PIPE, \ 

139 stderr=subprocess.PIPE, \ 

140 shell=True, \ 

141 close_fds=True) 

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

143 stdout = str(stdout) 

144 stderr = str(stderr) 

145 rc = proc.returncode 

146 if log: 

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

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

149 expectedRC = [expectedRC] 

150 if not rc in expectedRC: 

151 reason = stderr.strip() 

152 if stdout.strip(): 

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

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

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

156 

157 if ret == Util.RET_RC: 

158 return rc 

159 if ret == Util.RET_STDERR: 

160 return stderr 

161 return stdout 

162 doexec = staticmethod(doexec) 

163 

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

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

166 so""" 

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

168 resultFlag = IPCFlag(ns) 

169 resultFlag.clearAll() 

170 pid = os.fork() 

171 if pid: 

172 startTime = _time() 

173 try: 

174 while True: 

175 if resultFlag.test("success"): 

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

177 resultFlag.clear("success") 

178 return 

179 if resultFlag.test("failure"): 

180 resultFlag.clear("failure") 

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

182 if abortTest() or abortSignaled: 

183 os.killpg(pid, signal.SIGKILL) 

184 raise AbortException("Aborting due to signal") 

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

186 os.killpg(pid, signal.SIGKILL) 

187 resultFlag.clearAll() 

188 raise util.SMException("Timed out") 

189 time.sleep(pollInterval) 

190 finally: 

191 wait_pid = 0 

192 rc = -1 

193 count = 0 

194 while wait_pid == 0 and count < 10: 

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

196 if wait_pid == 0: 

197 time.sleep(2) 

198 count += 1 

199 

200 if wait_pid == 0: 

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

202 else: 

203 os.setpgrp() 

204 try: 

205 if func() == ret: 

206 resultFlag.set("success") 

207 else: 

208 resultFlag.set("failure") 

209 except Exception as e: 

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

211 resultFlag.set("failure") 

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

213 os._exit(0) 

214 runAbortable = staticmethod(runAbortable) 

215 

216 def num2str(number): 

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

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

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

220 return "%s" % number 

221 num2str = staticmethod(num2str) 

222 

223 def numBits(val): 

224 count = 0 

225 while val: 

226 count += val & 1 

227 val = val >> 1 

228 return count 

229 numBits = staticmethod(numBits) 

230 

231 def countBits(bitmap1, bitmap2): 

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

233 len1 = len(bitmap1) 

234 len2 = len(bitmap2) 

235 lenLong = len1 

236 lenShort = len2 

237 bitmapLong = bitmap1 

238 if len2 > len1: 

239 lenLong = len2 

240 lenShort = len1 

241 bitmapLong = bitmap2 

242 

243 count = 0 

244 for i in range(lenShort): 

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

246 count += Util.numBits(val) 

247 

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

249 val = bitmapLong[i] 

250 count += Util.numBits(val) 

251 return count 

252 countBits = staticmethod(countBits) 

253 

254 def getThisScript(): 

255 thisScript = util.get_real_path(__file__) 

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

257 thisScript = thisScript[:-1] 

258 return thisScript 

259 getThisScript = staticmethod(getThisScript) 

260 

261 

262################################################################################ 

263# 

264# XAPI 

265# 

266class XAPI: 

267 USER = "root" 

268 PLUGIN_ON_SLAVE = "on-slave" 

269 

270 CONFIG_SM = 0 

271 CONFIG_OTHER = 1 

272 CONFIG_ON_BOOT = 2 

273 CONFIG_ALLOW_CACHING = 3 

274 

275 CONFIG_NAME = { 

276 CONFIG_SM: "sm-config", 

277 CONFIG_OTHER: "other-config", 

278 CONFIG_ON_BOOT: "on-boot", 

279 CONFIG_ALLOW_CACHING: "allow_caching" 

280 } 

281 

282 class LookupError(util.SMException): 

283 pass 

284 

285 def getSession(): 

286 session = XenAPI.xapi_local() 

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

288 return session 

289 getSession = staticmethod(getSession) 

290 

291 def __init__(self, session, srUuid): 

292 self.sessionPrivate = False 

293 self.session = session 

294 if self.session is None: 

295 self.session = self.getSession() 

296 self.sessionPrivate = True 

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

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

299 self.hostUuid = util.get_this_host() 

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

301 self.task = None 

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

303 

304 def __del__(self): 

305 if self.sessionPrivate: 

306 self.session.xenapi.session.logout() 

307 

308 def isPluggedHere(self): 

309 pbds = self.getAttachedPBDs() 

310 for pbdRec in pbds: 

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

312 return True 

313 return False 

314 

315 def poolOK(self): 

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

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

318 if not host_rec["enabled"]: 

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

320 return False 

321 return True 

322 

323 def isMaster(self): 

324 if self.srRecord["shared"]: 

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

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

327 else: 

328 pbds = self.getAttachedPBDs() 

329 if len(pbds) < 1: 

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

331 elif len(pbds) > 1: 

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

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

334 

335 def getAttachedPBDs(self): 

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

337 attached""" 

338 attachedPBDs = [] 

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

340 for pbdRec in pbds.values(): 

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

342 attachedPBDs.append(pbdRec) 

343 return attachedPBDs 

344 

345 def getOnlineHosts(self): 

346 return util.get_online_hosts(self.session) 

347 

348 def ensureInactive(self, hostRef, args): 

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

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

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

352 

353 def getRecordHost(self, hostRef): 

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

355 

356 def _getRefVDI(self, uuid): 

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

358 

359 def getRefVDI(self, vdi): 

360 return self._getRefVDI(vdi.uuid) 

361 

362 def getRecordVDI(self, uuid): 

363 try: 

364 ref = self._getRefVDI(uuid) 

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

366 except XenAPI.Failure: 

367 return None 

368 

369 def singleSnapshotVDI(self, vdi): 

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

371 {"type": "internal"}) 

372 

373 def forgetVDI(self, srUuid, vdiUuid): 

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

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

376 try: 

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

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

379 except XenAPI.Failure: 

380 pass 

381 

382 def getConfigVDI(self, vdi, key): 

383 kind = vdi.CONFIG_TYPE[key] 

384 if kind == self.CONFIG_SM: 

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

386 elif kind == self.CONFIG_OTHER: 

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

388 elif kind == self.CONFIG_ON_BOOT: 

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

390 elif kind == self.CONFIG_ALLOW_CACHING: 

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

392 else: 

393 assert(False) 

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

395 return cfg 

396 

397 def removeFromConfigVDI(self, vdi, key): 

398 kind = vdi.CONFIG_TYPE[key] 

399 if kind == self.CONFIG_SM: 

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

401 elif kind == self.CONFIG_OTHER: 

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

403 else: 

404 assert(False) 

405 

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

407 kind = vdi.CONFIG_TYPE[key] 

408 if kind == self.CONFIG_SM: 

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

410 elif kind == self.CONFIG_OTHER: 

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

412 else: 

413 assert(False) 

414 

415 def isSnapshot(self, vdi): 

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

417 

418 def markCacheSRsDirty(self): 

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

420 'field "local_cache_enabled" = "true"') 

421 for sr_ref in sr_refs: 

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

423 util.set_dirty(self.session, sr_ref) 

424 

425 def srUpdate(self): 

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

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

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

429 cancelTask = True 

430 try: 

431 for i in range(60): 

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

433 if not status == "pending": 

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

435 cancelTask = False 

436 return 

437 if abortFlag.test(FLAG_TYPE_ABORT): 

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

439 try: 

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

441 cancelTask = False 

442 Util.log("Task cancelled") 

443 except: 

444 pass 

445 return 

446 time.sleep(1) 

447 finally: 

448 if cancelTask: 

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

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

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

452 

453 def update_task(self): 

454 self.session.xenapi.task.set_other_config( 

455 self.task, 

456 { 

457 "applies_to": self._srRef 

458 }) 

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

460 if (total > 0): 

461 self.session.xenapi.task.set_progress( 

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

463 

464 def create_task(self, label, description): 

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

466 self.update_task() 

467 

468 def update_task_progress(self, key, value): 

469 self.task_progress[key] = value 

470 if self.task: 

471 self.update_task() 

472 

473 def set_task_status(self, status): 

474 if self.task: 

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

476 

477 

478################################################################################ 

479# 

480# VDI 

481# 

482class VDI(object): 

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

484 

485 POLL_INTERVAL = 1 

486 POLL_TIMEOUT = 30 

487 DEVICE_MAJOR = 202 

488 DRIVER_NAME_VHD = "vhd" 

489 

490 # config keys & values 

491 DB_VHD_PARENT = "vhd-parent" 

492 DB_VDI_TYPE = "vdi_type" 

493 DB_VHD_BLOCKS = "vhd-blocks" 

494 DB_VDI_PAUSED = "paused" 

495 DB_VDI_RELINKING = "relinking" 

496 DB_VDI_ACTIVATING = "activating" 

497 DB_GC = "gc" 

498 DB_COALESCE = "coalesce" 

499 DB_LEAFCLSC = "leaf-coalesce" # config key 

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

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

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

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

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

505 # might be used by external components. 

506 DB_ONBOOT = "on-boot" 

507 ONBOOT_RESET = "reset" 

508 DB_ALLOW_CACHING = "allow_caching" 

509 

510 CONFIG_TYPE = { 

511 DB_VHD_PARENT: XAPI.CONFIG_SM, 

512 DB_VDI_TYPE: XAPI.CONFIG_SM, 

513 DB_VHD_BLOCKS: XAPI.CONFIG_SM, 

514 DB_VDI_PAUSED: XAPI.CONFIG_SM, 

515 DB_VDI_RELINKING: XAPI.CONFIG_SM, 

516 DB_VDI_ACTIVATING: XAPI.CONFIG_SM, 

517 DB_GC: XAPI.CONFIG_OTHER, 

518 DB_COALESCE: XAPI.CONFIG_OTHER, 

519 DB_LEAFCLSC: XAPI.CONFIG_OTHER, 

520 DB_ONBOOT: XAPI.CONFIG_ON_BOOT, 

521 DB_ALLOW_CACHING: XAPI.CONFIG_ALLOW_CACHING, 

522 } 

523 

524 LIVE_LEAF_COALESCE_MAX_SIZE = 20 * 1024 * 1024 # bytes 

525 LIVE_LEAF_COALESCE_TIMEOUT = 10 # seconds 

526 TIMEOUT_SAFETY_MARGIN = 0.5 # extra margin when calculating 

527 # feasibility of leaf coalesce 

528 

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

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

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

532 

533 STR_TREE_INDENT = 4 

534 

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

536 self.sr = sr 

537 self.scanError = True 

538 self.uuid = uuid 

539 self.raw = raw 

540 self.fileName = "" 

541 self.parentUuid = "" 

542 self.sizeVirt = -1 

543 self._sizeVHD = -1 

544 self._sizeAllocated = -1 

545 self.hidden = False 

546 self.parent = None 

547 self.children = [] 

548 self._vdiRef = None 

549 self._clearRef() 

550 

551 @staticmethod 

552 def extractUuid(path): 

553 raise NotImplementedError("Implement in sub class") 

554 

555 def load(self, info=None): 

556 """Load VDI info""" 

557 pass # abstract 

558 

559 def getDriverName(self): 

560 return self.DRIVER_NAME_VHD 

561 

562 def getRef(self): 

563 if self._vdiRef is None: 

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

565 return self._vdiRef 

566 

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

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

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

570 val = config 

571 else: 

572 val = config.get(key) 

573 if val: 

574 return val 

575 return default 

576 

577 def setConfig(self, key, val): 

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

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

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

581 

582 def delConfig(self, key): 

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

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

585 

586 def ensureUnpaused(self): 

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

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

589 self.unpause() 

590 

591 def pause(self, failfast=False): 

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

593 self.uuid, failfast): 

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

595 

596 def _report_tapdisk_unpause_error(self): 

597 try: 

598 xapi = self.sr.xapi.session.xenapi 

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

600 msg_name = "failed to unpause tapdisk" 

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

602 "VMs using this tapdisk have lost access " \ 

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

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

605 except Exception as e: 

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

607 

608 def unpause(self): 

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

610 self.uuid): 

611 self._report_tapdisk_unpause_error() 

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

613 

614 def refresh(self, ignoreNonexistent=True): 

615 """Pause-unpause in one step""" 

616 self.sr.lock() 

617 try: 

618 try: 

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

620 self.sr.uuid, self.uuid): 

621 self._report_tapdisk_unpause_error() 

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

623 except XenAPI.Failure as e: 

624 if util.isInvalidVDI(e) and ignoreNonexistent: 

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

626 return 

627 raise 

628 finally: 

629 self.sr.unlock() 

630 

631 def isSnapshot(self): 

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

633 

634 def isAttachedRW(self): 

635 return util.is_attached_rw( 

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

637 

638 def getVHDBlocks(self): 

639 val = self.updateBlockInfo() 

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

641 return bitmap 

642 

643 def isCoalesceable(self): 

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

645 return not self.scanError and \ 

646 self.parent and \ 

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

648 self.hidden and \ 

649 len(self.children) > 0 

650 

651 def isLeafCoalesceable(self): 

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

653 return not self.scanError and \ 

654 self.parent and \ 

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

656 not self.hidden and \ 

657 len(self.children) == 0 

658 

659 def canLiveCoalesce(self, speed): 

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

661 isLeafCoalesceable() already""" 

662 feasibleSize = False 

663 allowedDownTime = \ 

664 self.TIMEOUT_SAFETY_MARGIN * self.LIVE_LEAF_COALESCE_TIMEOUT 

665 vhd_size = self.getAllocatedSize() 

666 if speed: 

667 feasibleSize = \ 

668 vhd_size // speed < allowedDownTime 

669 else: 

670 feasibleSize = \ 

671 vhd_size < self.LIVE_LEAF_COALESCE_MAX_SIZE 

672 

673 return (feasibleSize or 

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

675 

676 def getAllPrunable(self): 

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

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

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

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

681 # some tapdisks could still be using the file. 

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

683 return [] 

684 if not self.scanError and self.hidden: 

685 return [self] 

686 return [] 

687 

688 thisPrunable = True 

689 vdiList = [] 

690 for child in self.children: 

691 childList = child.getAllPrunable() 

692 vdiList.extend(childList) 

693 if child not in childList: 

694 thisPrunable = False 

695 

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

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

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

699 # 

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

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

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

703 # 

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

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

706 # executed but in `_liveLeafCoalesce`. 

707 

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

709 vdiList.append(self) 

710 return vdiList 

711 

712 def getSizeVHD(self): 

713 return self._sizeVHD 

714 

715 def getAllocatedSize(self): 

716 return self._sizeAllocated 

717 

718 def getTreeRoot(self): 

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

720 root = self 

721 while root.parent: 

722 root = root.parent 

723 return root 

724 

725 def getTreeHeight(self): 

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

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

728 return 1 

729 

730 maxChildHeight = 0 

731 for child in self.children: 

732 childHeight = child.getTreeHeight() 

733 if childHeight > maxChildHeight: 

734 maxChildHeight = childHeight 

735 

736 return maxChildHeight + 1 

737 

738 def getAllLeaves(self): 

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

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

741 return [self] 

742 

743 leaves = [] 

744 for child in self.children: 

745 leaves.extend(child.getAllLeaves()) 

746 return leaves 

747 

748 def updateBlockInfo(self): 

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

750 self.setConfig(VDI.DB_VHD_BLOCKS, val) 

751 return val 

752 

753 def rename(self, uuid): 

754 "Rename the VDI file" 

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

756 self._clearRef() 

757 oldUuid = self.uuid 

758 self.uuid = uuid 

759 self.children = [] 

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

761 del self.sr.vdis[oldUuid] 

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

763 

764 def delete(self): 

765 "Physically delete the VDI" 

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

767 lock.Lock.cleanupAll(self.uuid) 

768 self._clear() 

769 

770 def getParent(self): 

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

772 

773 def repair(self, parent): 

774 vhdutil.repair(parent) 

775 

776 def __str__(self): 

777 strHidden = "" 

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

779 strHidden = "*" 

780 strSizeVirt = "?" 

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

782 strSizeVirt = Util.num2str(self.sizeVirt) 

783 strSizeVHD = "?" 

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

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

786 strSizeAllocated = "?" 

787 if self._sizeAllocated >= 0: 

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

789 strType = "" 

790 if self.raw: 

791 strType = "[RAW]" 

792 strSizeVHD = "" 

793 

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

795 strSizeVHD, strSizeAllocated, strType) 

796 

797 def validate(self, fast=False): 

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

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

800 

801 def _clear(self): 

802 self.uuid = "" 

803 self.path = "" 

804 self.parentUuid = "" 

805 self.parent = None 

806 self._clearRef() 

807 

808 def _clearRef(self): 

809 self._vdiRef = None 

810 

811 def _doCoalesce(self): 

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

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

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

815 were coalescing""" 

816 self.validate() 

817 self.parent.validate(True) 

818 self.parent._increaseSizeVirt(self.sizeVirt) 

819 self.sr._updateSlavesOnResize(self.parent) 

820 self._coalesceVHD(0) 

821 self.parent.validate(True) 

822 #self._verifyContents(0) 

823 self.parent.updateBlockInfo() 

824 

825 def _verifyContents(self, timeOut): 

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

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

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

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

830 Util.log(" Coalesce verification succeeded") 

831 

832 def _runTapdiskDiff(self): 

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

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

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

836 Util.doexec(cmd, 0) 

837 return True 

838 

839 def _reportCoalesceError(vdi, ce): 

840 """Reports a coalesce error to XenCenter. 

841 

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

843 ce: the CommandException that was raised""" 

844 

845 msg_name = os.strerror(ce.code) 

846 if ce.code == errno.ENOSPC: 

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

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

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

850 # actions. 

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

852 elif ce.code == errno.EIO: 

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

854 else: 

855 msg_body = '' 

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

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

858 

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

860 xapi = vdi.sr.xapi.session.xenapi 

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

862 oth_cfg = xapi.SR.get_other_config(sr_ref) 

863 if COALESCE_ERR_RATE_TAG in oth_cfg: 

864 coalesce_err_rate = float(oth_cfg[COALESCE_ERR_RATE_TAG]) 

865 else: 

866 coalesce_err_rate = DEFAULT_COALESCE_ERR_RATE 

867 

868 xcmsg = False 

869 if coalesce_err_rate == 0: 

870 xcmsg = True 

871 elif coalesce_err_rate > 0: 

872 now = datetime.datetime.now() 

873 sm_cfg = xapi.SR.get_sm_config(sr_ref) 

874 if COALESCE_LAST_ERR_TAG in sm_cfg: 

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

876 # messages in seconds) 

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

878 last = datetime.datetime.fromtimestamp( 

879 float(sm_cfg[COALESCE_LAST_ERR_TAG])) 

880 if now - last >= spm: 

881 xapi.SR.remove_from_sm_config(sr_ref, 

882 COALESCE_LAST_ERR_TAG) 

883 xcmsg = True 

884 else: 

885 xcmsg = True 

886 if xcmsg: 

887 xapi.SR.add_to_sm_config(sr_ref, COALESCE_LAST_ERR_TAG, 

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

889 if xcmsg: 

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

891 _reportCoalesceError = staticmethod(_reportCoalesceError) 

892 

893 def coalesce(self): 

894 # size is returned in sectors 

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

896 

897 def _doCoalesceVHD(vdi): 

898 try: 

899 startTime = time.time() 

900 vhdSize = vdi.getAllocatedSize() 

901 coalesced_size = vdi.coalesce() 

902 endTime = time.time() 

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

904 except util.CommandException as ce: 

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

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

907 # reported by anyone. 

908 try: 

909 # Report coalesce errors back to user via XC 

910 VDI._reportCoalesceError(vdi, ce) 

911 except Exception as e: 

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

913 raise ce 

914 except: 

915 raise 

916 _doCoalesceVHD = staticmethod(_doCoalesceVHD) 

917 

918 def _vdi_is_raw(self, vdi_path): 

919 """ 

920 Given path to vdi determine if it is raw 

921 """ 

922 uuid = self.extractUuid(vdi_path) 

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

924 

925 def _coalesceVHD(self, timeOut): 

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

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

928 try: 

929 util.fistpoint.activate_custom_fn( 

930 "cleanup_coalesceVHD_inject_failure", 

931 util.inject_failure) 

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

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

934 except: 

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

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

937 # Try a repair and reraise the exception 

938 parent = "" 

939 try: 

940 parent = self.getParent() 

941 if not self._vdi_is_raw(parent): 

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

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

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

945 self.repair(parent) 

946 except Exception as e: 

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

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

949 (parent, self.path, e)) 

950 raise 

951 

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

953 

954 def _relinkSkip(self): 

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

956 abortFlag = IPCFlag(self.sr.uuid) 

957 for child in self.children: 

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

959 raise AbortException("Aborting due to signal") 

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

961 (child, self, self.parent)) 

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

963 child._setParent(self.parent) 

964 self.children = [] 

965 

966 def _reloadChildren(self, vdiSkip): 

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

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

969 abortFlag = IPCFlag(self.sr.uuid) 

970 for child in self.children: 

971 if child == vdiSkip: 

972 continue 

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

974 raise AbortException("Aborting due to signal") 

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

976 child._reload() 

977 

978 def _reload(self): 

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

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

981 child._reload() 

982 

983 # only leaves can be attached 

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

985 try: 

986 self.delConfig(VDI.DB_VDI_RELINKING) 

987 except XenAPI.Failure as e: 

988 if not util.isInvalidVDI(e): 

989 raise 

990 self.refresh() 

991 

992 def _tagChildrenForRelink(self): 

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

994 retries = 0 

995 try: 

996 while retries < 15: 

997 retries += 1 

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

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

1000 self.uuid) 

1001 else: 

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

1003 

1004 if self.getConfig(VDI.DB_VDI_ACTIVATING): 

1005 self.delConfig(VDI.DB_VDI_RELINKING) 

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

1007 self.uuid) 

1008 else: 

1009 return 

1010 time.sleep(2) 

1011 

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

1013 except XenAPI.Failure as e: 

1014 if not util.isInvalidVDI(e): 

1015 raise 

1016 

1017 for child in self.children: 

1018 child._tagChildrenForRelink() 

1019 

1020 def _loadInfoParent(self): 

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

1022 if ret: 

1023 self.parentUuid = ret 

1024 

1025 def _setParent(self, parent): 

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

1027 self.parent = parent 

1028 self.parentUuid = parent.uuid 

1029 parent.children.append(self) 

1030 try: 

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

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

1033 (self.uuid, self.parentUuid)) 

1034 except: 

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

1036 (self.uuid, self.parentUuid)) 

1037 

1038 def _loadInfoHidden(self): 

1039 hidden = vhdutil.getHidden(self.path) 

1040 self.hidden = (hidden != 0) 

1041 

1042 def _setHidden(self, hidden=True): 

1043 vhdutil.setHidden(self.path, hidden) 

1044 self.hidden = hidden 

1045 

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

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

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

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

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

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

1052 call with atomic = False""" 

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

1054 return 

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

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

1057 

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

1059 if (size <= msize): 

1060 vhdutil.setSizeVirtFast(self.path, size) 

1061 else: 

1062 if atomic: 

1063 vdiList = self._getAllSubtree() 

1064 self.sr.lock() 

1065 try: 

1066 self.sr.pauseVDIs(vdiList) 

1067 try: 

1068 self._setSizeVirt(size) 

1069 finally: 

1070 self.sr.unpauseVDIs(vdiList) 

1071 finally: 

1072 self.sr.unlock() 

1073 else: 

1074 self._setSizeVirt(size) 

1075 

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

1077 

1078 def _setSizeVirt(self, size): 

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

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

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

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

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

1084 

1085 def _queryVHDBlocks(self): 

1086 return vhdutil.getBlockBitmap(self.path) 

1087 

1088 def _getCoalescedSizeData(self): 

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

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

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

1092 upper bound)""" 

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

1094 # was writable all this time 

1095 self.delConfig(VDI.DB_VHD_BLOCKS) 

1096 blocksChild = self.getVHDBlocks() 

1097 blocksParent = self.parent.getVHDBlocks() 

1098 numBlocks = Util.countBits(blocksChild, blocksParent) 

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

1100 sizeData = numBlocks * vhdutil.VHD_BLOCK_SIZE 

1101 assert(sizeData <= self.sizeVirt) 

1102 return sizeData 

1103 

1104 def _calcExtraSpaceForCoalescing(self): 

1105 sizeData = self._getCoalescedSizeData() 

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

1107 vhdutil.calcOverheadEmpty(self.sizeVirt) 

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

1109 return sizeCoalesced - self.parent.getSizeVHD() 

1110 

1111 def _calcExtraSpaceForLeafCoalescing(self): 

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

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

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

1115 return self._calcExtraSpaceForCoalescing() 

1116 

1117 def _calcExtraSpaceForSnapshotCoalescing(self): 

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

1119 snapshot-coalesce this VDI""" 

1120 return self._calcExtraSpaceForCoalescing() + \ 

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

1122 

1123 def _getAllSubtree(self): 

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

1125 vdiList = [self] 

1126 for child in self.children: 

1127 vdiList.extend(child._getAllSubtree()) 

1128 return vdiList 

1129 

1130 

1131class FileVDI(VDI): 

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

1133 

1134 @staticmethod 

1135 def extractUuid(path): 

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

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

1138 path.endswith(vhdutil.FILE_EXTN_RAW)): 

1139 return None 

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

1141 vhdutil.FILE_EXTN_RAW, "") 

1142 # TODO: validate UUID format 

1143 return uuid 

1144 

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

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

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

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

1149 else: 

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

1151 

1152 def load(self, info=None): 

1153 if not info: 

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

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

1156 try: 

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

1158 except util.SMException: 

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

1160 return 

1161 self.parent = None 

1162 self.children = [] 

1163 self.parentUuid = info.parentUuid 

1164 self.sizeVirt = info.sizeVirt 

1165 self._sizeVHD = info.sizePhys 

1166 self._sizeAllocated = info.sizeAllocated 

1167 self.hidden = info.hidden 

1168 self.scanError = False 

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

1170 (self.uuid, vhdutil.FILE_EXTN_VHD)) 

1171 

1172 def rename(self, uuid): 

1173 oldPath = self.path 

1174 VDI.rename(self, uuid) 

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

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

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

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

1179 os.rename(oldPath, self.path) 

1180 

1181 def delete(self): 

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

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

1184 self.uuid) 

1185 try: 

1186 self.sr.lock() 

1187 try: 

1188 os.unlink(self.path) 

1189 self.sr.forgetVDI(self.uuid) 

1190 finally: 

1191 self.sr.unlock() 

1192 except OSError: 

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

1194 VDI.delete(self) 

1195 

1196 def getAllocatedSize(self): 

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

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

1199 return self._sizeAllocated 

1200 

1201 

1202class LVHDVDI(VDI): 

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

1204 

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

1206 DRIVER_NAME_RAW = "aio" 

1207 

1208 def load(self, vdiInfo): 

1209 self.parent = None 

1210 self.children = [] 

1211 self._sizeVHD = -1 

1212 self._sizeAllocated = -1 

1213 self.scanError = vdiInfo.scanError 

1214 self.sizeLV = vdiInfo.sizeLV 

1215 self.sizeVirt = vdiInfo.sizeVirt 

1216 self.fileName = vdiInfo.lvName 

1217 self.lvActive = vdiInfo.lvActive 

1218 self.lvOpen = vdiInfo.lvOpen 

1219 self.lvReadonly = vdiInfo.lvReadonly 

1220 self.hidden = vdiInfo.hidden 

1221 self.parentUuid = vdiInfo.parentUuid 

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

1223 

1224 @staticmethod 

1225 def extractUuid(path): 

1226 return lvhdutil.extractUuid(path) 

1227 

1228 def getDriverName(self): 

1229 if self.raw: 

1230 return self.DRIVER_NAME_RAW 

1231 return self.DRIVER_NAME_VHD 

1232 

1233 def inflate(self, size): 

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

1235 if self.raw: 

1236 return 

1237 self._activate() 

1238 self.sr.lock() 

1239 try: 

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

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

1242 finally: 

1243 self.sr.unlock() 

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

1245 self._sizeVHD = -1 

1246 self._sizeAllocated = -1 

1247 

1248 def deflate(self): 

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

1250 if self.raw: 

1251 return 

1252 self._activate() 

1253 self.sr.lock() 

1254 try: 

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

1256 finally: 

1257 self.sr.unlock() 

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

1259 self._sizeVHD = -1 

1260 self._sizeAllocated = -1 

1261 

1262 def inflateFully(self): 

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

1264 

1265 def inflateParentForCoalesce(self): 

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

1267 coalescing""" 

1268 if self.parent.raw: 

1269 return 

1270 inc = self._calcExtraSpaceForCoalescing() 

1271 if inc > 0: 

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

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

1274 

1275 def updateBlockInfo(self): 

1276 if not self.raw: 

1277 return VDI.updateBlockInfo(self) 

1278 

1279 def rename(self, uuid): 

1280 oldUuid = self.uuid 

1281 oldLVName = self.fileName 

1282 VDI.rename(self, uuid) 

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

1284 if self.raw: 

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

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

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

1288 

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

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

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

1292 

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

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

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

1296 RefCounter.reset(oldUuid, ns) 

1297 

1298 def delete(self): 

1299 if len(self.children) > 0: 

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

1301 self.uuid) 

1302 self.sr.lock() 

1303 try: 

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

1305 self.sr.forgetVDI(self.uuid) 

1306 finally: 

1307 self.sr.unlock() 

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

1309 VDI.delete(self) 

1310 

1311 def getSizeVHD(self): 

1312 if self._sizeVHD == -1: 

1313 self._loadInfoSizeVHD() 

1314 return self._sizeVHD 

1315 

1316 def _loadInfoSizeVHD(self): 

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

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

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

1320 coalescing.""" 

1321 if self.raw: 

1322 return 

1323 self._activate() 

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

1325 if self._sizeVHD <= 0: 

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

1327 (self, self._sizeVHD)) 

1328 

1329 def getAllocatedSize(self): 

1330 if self._sizeAllocated == -1: 

1331 self._loadInfoSizeAllocated() 

1332 return self._sizeAllocated 

1333 

1334 def _loadInfoSizeAllocated(self): 

1335 """ 

1336 Get the allocated size of the VHD volume. 

1337 """ 

1338 if self.raw: 

1339 return 

1340 self._activate() 

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

1342 

1343 def _loadInfoHidden(self): 

1344 if self.raw: 

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

1346 else: 

1347 VDI._loadInfoHidden(self) 

1348 

1349 def _setHidden(self, hidden=True): 

1350 if self.raw: 

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

1352 self.hidden = hidden 

1353 else: 

1354 VDI._setHidden(self, hidden) 

1355 

1356 def __str__(self): 

1357 strType = "VHD" 

1358 if self.raw: 

1359 strType = "RAW" 

1360 strHidden = "" 

1361 if self.hidden: 

1362 strHidden = "*" 

1363 strSizeVHD = "" 

1364 if self._sizeVHD > 0: 

1365 strSizeVHD = Util.num2str(self._sizeVHD) 

1366 strSizeAllocated = "" 

1367 if self._sizeAllocated >= 0: 

1368 strSizeAllocated = Util.num2str(self._sizeAllocated) 

1369 strActive = "n" 

1370 if self.lvActive: 

1371 strActive = "a" 

1372 if self.lvOpen: 

1373 strActive += "o" 

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

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

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

1377 

1378 def validate(self, fast=False): 

1379 if not self.raw: 

1380 VDI.validate(self, fast) 

1381 

1382 def _doCoalesce(self): 

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

1384 try: 

1385 self._activateChain() 

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

1387 self.parent.validate() 

1388 self.inflateParentForCoalesce() 

1389 VDI._doCoalesce(self) 

1390 finally: 

1391 self.parent._loadInfoSizeVHD() 

1392 self.parent.deflate() 

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

1394 

1395 def _setParent(self, parent): 

1396 self._activate() 

1397 if self.lvReadonly: 

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

1399 

1400 try: 

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

1402 finally: 

1403 if self.lvReadonly: 

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

1405 self._deactivate() 

1406 self.parent = parent 

1407 self.parentUuid = parent.uuid 

1408 parent.children.append(self) 

1409 try: 

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

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

1412 (self.uuid, self.parentUuid)) 

1413 except: 

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

1415 (self.parentUuid, self.uuid)) 

1416 

1417 def _activate(self): 

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

1419 

1420 def _activateChain(self): 

1421 vdi = self 

1422 while vdi: 

1423 vdi._activate() 

1424 vdi = vdi.parent 

1425 

1426 def _deactivate(self): 

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

1428 

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

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

1431 self._activate() 

1432 if not self.raw: 

1433 VDI._increaseSizeVirt(self, size, atomic) 

1434 return 

1435 

1436 # raw VDI case 

1437 offset = self.sizeLV 

1438 if self.sizeVirt < size: 

1439 oldSize = self.sizeLV 

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

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

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

1443 offset = oldSize 

1444 unfinishedZero = False 

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

1446 if jval: 

1447 unfinishedZero = True 

1448 offset = int(jval) 

1449 length = self.sizeLV - offset 

1450 if not length: 

1451 return 

1452 

1453 if unfinishedZero: 

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

1455 else: 

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

1457 str(offset)) 

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

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

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

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

1462 VDI.POLL_INTERVAL, 0) 

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

1464 

1465 def _setSizeVirt(self, size): 

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

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

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

1469 self._activate() 

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

1471 vhdutil.MAX_VHD_JOURNAL_SIZE) 

1472 try: 

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

1474 size, jFile) 

1475 finally: 

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

1477 

1478 def _queryVHDBlocks(self): 

1479 self._activate() 

1480 return VDI._queryVHDBlocks(self) 

1481 

1482 def _calcExtraSpaceForCoalescing(self): 

1483 if self.parent.raw: 

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

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

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

1487 return sizeCoalesced - self.parent.sizeLV 

1488 

1489 def _calcExtraSpaceForLeafCoalescing(self): 

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

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

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

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

1494 return self._calcExtraSpaceForCoalescing() - deflateDiff 

1495 

1496 def _calcExtraSpaceForSnapshotCoalescing(self): 

1497 return self._calcExtraSpaceForCoalescing() + \ 

1498 lvhdutil.calcSizeLV(self.getSizeVHD()) 

1499 

1500 

1501class LinstorVDI(VDI): 

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

1503 

1504 VOLUME_LOCK_TIMEOUT = 30 

1505 

1506 def load(self, info=None): 

1507 self.parentUuid = info.parentUuid 

1508 self.scanError = True 

1509 self.parent = None 

1510 self.children = [] 

1511 

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

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

1514 

1515 if not info: 

1516 try: 

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

1518 except util.SMException: 

1519 Util.log( 

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

1521 ) 

1522 return 

1523 

1524 self.parentUuid = info.parentUuid 

1525 self.sizeVirt = info.sizeVirt 

1526 self._sizeVHD = -1 

1527 self._sizeAllocated = -1 

1528 self.drbd_size = -1 

1529 self.hidden = info.hidden 

1530 self.scanError = False 

1531 self.vdi_type = vhdutil.VDI_TYPE_VHD 

1532 

1533 def getSizeVHD(self, fetch=False): 

1534 if self._sizeVHD < 0 or fetch: 

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

1536 return self._sizeVHD 

1537 

1538 def getDrbdSize(self, fetch=False): 

1539 if self.drbd_size < 0 or fetch: 

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

1541 return self.drbd_size 

1542 

1543 def getAllocatedSize(self): 

1544 if self._sizeAllocated == -1: 

1545 if not self.raw: 

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

1547 return self._sizeAllocated 

1548 

1549 def inflate(self, size): 

1550 if self.raw: 

1551 return 

1552 self.sr.lock() 

1553 try: 

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

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

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

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

1558 finally: 

1559 self.sr.unlock() 

1560 self.drbd_size = -1 

1561 self._sizeVHD = -1 

1562 self._sizeAllocated = -1 

1563 

1564 def deflate(self): 

1565 if self.raw: 

1566 return 

1567 self.sr.lock() 

1568 try: 

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

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

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

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

1573 finally: 

1574 self.sr.unlock() 

1575 self.drbd_size = -1 

1576 self._sizeVHD = -1 

1577 self._sizeAllocated = -1 

1578 

1579 def inflateFully(self): 

1580 if not self.raw: 

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

1582 

1583 def rename(self, uuid): 

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

1585 self.uuid, uuid, self.path 

1586 )) 

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

1588 VDI.rename(self, uuid) 

1589 

1590 def delete(self): 

1591 if len(self.children) > 0: 

1592 raise util.SMException( 

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

1594 ) 

1595 self.sr.lock() 

1596 try: 

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

1598 self.sr.forgetVDI(self.uuid) 

1599 finally: 

1600 self.sr.unlock() 

1601 VDI.delete(self) 

1602 

1603 def validate(self, fast=False): 

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

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

1606 

1607 def pause(self, failfast=False): 

1608 self.sr._linstor.ensure_volume_is_not_locked( 

1609 self.uuid, timeout=self.VOLUME_LOCK_TIMEOUT 

1610 ) 

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

1612 

1613 def coalesce(self): 

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

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

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

1617 

1618 def getParent(self): 

1619 return self.sr._vhdutil.get_parent( 

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

1621 ) 

1622 

1623 def repair(self, parent_uuid): 

1624 self.sr._vhdutil.force_repair( 

1625 self.sr._linstor.get_device_path(parent_uuid) 

1626 ) 

1627 

1628 def _relinkSkip(self): 

1629 abortFlag = IPCFlag(self.sr.uuid) 

1630 for child in self.children: 

1631 if abortFlag.test(FLAG_TYPE_ABORT): 

1632 raise AbortException('Aborting due to signal') 

1633 Util.log( 

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

1635 child, self, self.parent 

1636 ) 

1637 ) 

1638 

1639 session = child.sr.xapi.session 

1640 sr_uuid = child.sr.uuid 

1641 vdi_uuid = child.uuid 

1642 try: 

1643 self.sr._linstor.ensure_volume_is_not_locked( 

1644 vdi_uuid, timeout=self.VOLUME_LOCK_TIMEOUT 

1645 ) 

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

1647 child._setParent(self.parent) 

1648 finally: 

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

1650 self.children = [] 

1651 

1652 def _setParent(self, parent): 

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

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

1655 self.parent = parent 

1656 self.parentUuid = parent.uuid 

1657 parent.children.append(self) 

1658 try: 

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

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

1661 (self.uuid, self.parentUuid)) 

1662 except: 

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

1664 (self.uuid, self.parentUuid)) 

1665 

1666 def _doCoalesce(self): 

1667 try: 

1668 self._activateChain() 

1669 self.parent.validate() 

1670 self._inflateParentForCoalesce() 

1671 VDI._doCoalesce(self) 

1672 finally: 

1673 self.parent.deflate() 

1674 

1675 def _activateChain(self): 

1676 vdi = self 

1677 while vdi: 

1678 try: 

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

1680 except Exception as e: 

1681 # Use SMException to skip coalesce. 

1682 # Otherwise the GC is stopped... 

1683 raise util.SMException(str(e)) 

1684 vdi = vdi.parent 

1685 

1686 def _setHidden(self, hidden=True): 

1687 HIDDEN_TAG = 'hidden' 

1688 

1689 if self.raw: 

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

1691 HIDDEN_TAG: hidden 

1692 }) 

1693 self.hidden = hidden 

1694 else: 

1695 VDI._setHidden(self, hidden) 

1696 

1697 def _setSizeVirt(self, size): 

1698 jfile = self.uuid + '-jvhd' 

1699 self.sr._linstor.create_volume( 

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

1701 ) 

1702 try: 

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

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

1705 finally: 

1706 try: 

1707 self.sr._linstor.destroy_volume(jfile) 

1708 except Exception: 

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

1710 pass 

1711 

1712 def _queryVHDBlocks(self): 

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

1714 

1715 def _inflateParentForCoalesce(self): 

1716 if self.parent.raw: 

1717 return 

1718 inc = self._calcExtraSpaceForCoalescing() 

1719 if inc > 0: 

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

1721 

1722 def _calcExtraSpaceForCoalescing(self): 

1723 if self.parent.raw: 

1724 return 0 

1725 size_coalesced = LinstorVhdUtil.compute_volume_size( 

1726 self._getCoalescedSizeData(), self.vdi_type 

1727 ) 

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

1729 return size_coalesced - self.parent.getDrbdSize() 

1730 

1731 def _calcExtraSpaceForLeafCoalescing(self): 

1732 assert self.getDrbdSize() > 0 

1733 assert self.getSizeVHD() > 0 

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

1735 assert deflate_diff >= 0 

1736 return self._calcExtraSpaceForCoalescing() - deflate_diff 

1737 

1738 def _calcExtraSpaceForSnapshotCoalescing(self): 

1739 assert self.getSizeVHD() > 0 

1740 return self._calcExtraSpaceForCoalescing() + \ 

1741 LinstorVolumeManager.round_up_volume_size(self.getSizeVHD()) 

1742 

1743################################################################################ 

1744# 

1745# SR 

1746# 

1747class SR(object): 

1748 class LogFilter: 

1749 def __init__(self, sr): 

1750 self.sr = sr 

1751 self.stateLogged = False 

1752 self.prevState = {} 

1753 self.currState = {} 

1754 

1755 def logState(self): 

1756 changes = "" 

1757 self.currState.clear() 

1758 for vdi in self.sr.vdiTrees: 

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

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

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

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

1763 

1764 for uuid in self.prevState: 

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

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

1767 

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

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

1770 

1771 if len(changes) > 0: 

1772 if self.stateLogged: 

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

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

1775 else: 

1776 result += "no changes" 

1777 

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

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

1780 self.prevState.clear() 

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

1782 self.prevState[key] = val 

1783 self.stateLogged = True 

1784 

1785 def logNewVDI(self, uuid): 

1786 if self.stateLogged: 

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

1788 

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

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

1791 for child in vdi.children: 

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

1793 return treeStr 

1794 

1795 TYPE_FILE = "file" 

1796 TYPE_LVHD = "lvhd" 

1797 TYPE_LINSTOR = "linstor" 

1798 TYPES = [TYPE_LVHD, TYPE_FILE, TYPE_LINSTOR] 

1799 

1800 LOCK_RETRY_INTERVAL = 3 

1801 LOCK_RETRY_ATTEMPTS = 20 

1802 LOCK_RETRY_ATTEMPTS_LOCK = 100 

1803 

1804 SCAN_RETRY_ATTEMPTS = 3 

1805 

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

1807 TMP_RENAME_PREFIX = "OLD_" 

1808 

1809 KEY_OFFLINE_COALESCE_NEEDED = "leaf_coalesce_need_offline" 

1810 KEY_OFFLINE_COALESCE_OVERRIDE = "leaf_coalesce_offline_override" 

1811 

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

1813 xapi = XAPI(xapiSession, uuid) 

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

1815 if type == SR.TYPE_FILE: 

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

1817 elif type == SR.TYPE_LVHD: 

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

1819 elif type == SR.TYPE_LINSTOR: 

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

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

1822 getInstance = staticmethod(getInstance) 

1823 

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

1825 self.logFilter = self.LogFilter(self) 

1826 self.uuid = uuid 

1827 self.path = "" 

1828 self.name = "" 

1829 self.vdis = {} 

1830 self.vdiTrees = [] 

1831 self.journaler = None 

1832 self.xapi = xapi 

1833 self._locked = 0 

1834 self._srLock = None 

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

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

1837 else: 

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

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

1840 self._failedCoalesceTargets = [] 

1841 

1842 if not self.xapi.isPluggedHere(): 

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

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

1845 else: 

1846 if not self.wait_for_plug(): 

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

1848 

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

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

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

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

1853 

1854 def wait_for_plug(self): 

1855 for _ in range(1, 10): 

1856 time.sleep(2) 

1857 if self.xapi.isPluggedHere(): 

1858 return True 

1859 return False 

1860 

1861 def gcEnabled(self, refresh=True): 

1862 if refresh: 

1863 self.xapi.srRecord = \ 

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

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

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

1867 return False 

1868 return True 

1869 

1870 def scan(self, force=False): 

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

1872 update VDI objects if they already exist""" 

1873 pass # abstract 

1874 

1875 def scanLocked(self, force=False): 

1876 self.lock() 

1877 try: 

1878 self.scan(force) 

1879 finally: 

1880 self.unlock() 

1881 

1882 def getVDI(self, uuid): 

1883 return self.vdis.get(uuid) 

1884 

1885 def hasWork(self): 

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

1887 return True 

1888 if self.findCoalesceable(): 

1889 return True 

1890 if self.findLeafCoalesceable(): 

1891 return True 

1892 if self.needUpdateBlockInfo(): 

1893 return True 

1894 return False 

1895 

1896 def findCoalesceable(self): 

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

1898 (choosing one among all coalesceable candidates according to some 

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

1900 

1901 candidates = [] 

1902 

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

1904 if srSwitch == "false": 

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

1906 return candidates 

1907 

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

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

1910 for uuid in journals: 

1911 vdi = self.getVDI(uuid) 

1912 if vdi and vdi not in self._failedCoalesceTargets: 

1913 return vdi 

1914 

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

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

1917 candidates.append(vdi) 

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

1919 

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

1921 

1922 # pick one in the tallest tree 

1923 treeHeight = dict() 

1924 for c in candidates: 

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

1926 if treeHeight.get(height): 

1927 treeHeight[height].append(c) 

1928 else: 

1929 treeHeight[height] = [c] 

1930 

1931 freeSpace = self.getFreeSpace() 

1932 heights = list(treeHeight.keys()) 

1933 heights.sort(reverse=True) 

1934 for h in heights: 

1935 for c in treeHeight[h]: 

1936 spaceNeeded = c._calcExtraSpaceForCoalescing() 

1937 if spaceNeeded <= freeSpace: 

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

1939 return c 

1940 else: 

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

1942 (c, freeSpace)) 

1943 return None 

1944 

1945 def getSwitch(self, key): 

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

1947 

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

1949 srSwitch = self.getSwitch(switch) 

1950 ret = False 

1951 if srSwitch: 

1952 ret = srSwitch == condition 

1953 

1954 if ret: 

1955 Util.log(fail_msg) 

1956 

1957 return ret 

1958 

1959 def leafCoalesceForbidden(self): 

1960 return (self.forbiddenBySwitch(VDI.DB_COALESCE, 

1961 "false", 

1962 "Coalesce disabled for this SR") or 

1963 self.forbiddenBySwitch(VDI.DB_LEAFCLSC, 

1964 VDI.LEAFCLSC_DISABLED, 

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

1966 

1967 def findLeafCoalesceable(self): 

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

1969 

1970 candidates = [] 

1971 if self.leafCoalesceForbidden(): 

1972 return candidates 

1973 

1974 self.gatherLeafCoalesceable(candidates) 

1975 

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

1977 

1978 freeSpace = self.getFreeSpace() 

1979 for candidate in candidates: 

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

1981 # feasible for this candidate 

1982 spaceNeeded = candidate._calcExtraSpaceForSnapshotCoalescing() 

1983 spaceNeededLive = spaceNeeded 

1984 if spaceNeeded > freeSpace: 

1985 spaceNeededLive = candidate._calcExtraSpaceForLeafCoalescing() 

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

1987 spaceNeeded = spaceNeededLive 

1988 

1989 if spaceNeeded <= freeSpace: 

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

1991 return candidate 

1992 else: 

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

1994 (candidate, freeSpace)) 

1995 if spaceNeededLive <= freeSpace: 

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

1997 candidate.setConfig(VDI.DB_LEAFCLSC, 

1998 VDI.LEAFCLSC_OFFLINE) 

1999 

2000 return None 

2001 

2002 def gatherLeafCoalesceable(self, candidates): 

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

2004 if not vdi.isLeafCoalesceable(): 

2005 continue 

2006 if vdi in self._failedCoalesceTargets: 

2007 continue 

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

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

2010 continue 

2011 if vdi.getConfig(vdi.DB_ALLOW_CACHING): 

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

2013 continue 

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

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

2016 continue 

2017 if not (AUTO_ONLINE_LEAF_COALESCE_ENABLED or 

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

2019 continue 

2020 candidates.append(vdi) 

2021 

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

2023 """Coalesce vdi onto parent""" 

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

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

2026 return 

2027 

2028 try: 

2029 self._coalesce(vdi) 

2030 except util.SMException as e: 

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

2032 self.cleanup() 

2033 raise 

2034 else: 

2035 self._failedCoalesceTargets.append(vdi) 

2036 Util.logException("coalesce") 

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

2038 self.cleanup() 

2039 

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

2041 """Leaf-coalesce vdi onto parent""" 

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

2043 if dryRun: 

2044 return 

2045 

2046 try: 

2047 uuid = vdi.uuid 

2048 try: 

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

2050 self._coalesceLeaf(vdi) 

2051 finally: 

2052 vdi = self.getVDI(uuid) 

2053 if vdi: 

2054 vdi.delConfig(vdi.DB_LEAFCLSC) 

2055 except AbortException: 

2056 self.cleanup() 

2057 raise 

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

2059 self._failedCoalesceTargets.append(vdi) 

2060 Util.logException("leaf-coalesce") 

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

2062 self.cleanup() 

2063 

2064 def garbageCollect(self, dryRun=False): 

2065 vdiList = self.findGarbage() 

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

2067 for vdi in vdiList: 

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

2069 if not dryRun: 

2070 self.deleteVDIs(vdiList) 

2071 self.cleanupJournals(dryRun) 

2072 

2073 def findGarbage(self): 

2074 vdiList = [] 

2075 for vdi in self.vdiTrees: 

2076 vdiList.extend(vdi.getAllPrunable()) 

2077 return vdiList 

2078 

2079 def deleteVDIs(self, vdiList): 

2080 for vdi in vdiList: 

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

2082 raise AbortException("Aborting due to signal") 

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

2084 self.deleteVDI(vdi) 

2085 

2086 def deleteVDI(self, vdi): 

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

2088 del self.vdis[vdi.uuid] 

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

2090 vdi.parent.children.remove(vdi) 

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

2092 self.vdiTrees.remove(vdi) 

2093 vdi.delete() 

2094 

2095 def forgetVDI(self, vdiUuid): 

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

2097 

2098 def pauseVDIs(self, vdiList): 

2099 paused = [] 

2100 failed = False 

2101 for vdi in vdiList: 

2102 try: 

2103 vdi.pause() 

2104 paused.append(vdi) 

2105 except: 

2106 Util.logException("pauseVDIs") 

2107 failed = True 

2108 break 

2109 

2110 if failed: 

2111 self.unpauseVDIs(paused) 

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

2113 

2114 def unpauseVDIs(self, vdiList): 

2115 failed = False 

2116 for vdi in vdiList: 

2117 try: 

2118 vdi.unpause() 

2119 except: 

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

2121 failed = True 

2122 if failed: 

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

2124 

2125 def getFreeSpace(self): 

2126 return 0 

2127 

2128 def cleanup(self): 

2129 Util.log("In cleanup") 

2130 return 

2131 

2132 def __str__(self): 

2133 if self.name: 

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

2135 else: 

2136 ret = "%s" % self.uuid 

2137 return ret 

2138 

2139 def lock(self): 

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

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

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

2143 if not self._srLock: 

2144 return 

2145 

2146 if self._locked == 0: 

2147 abortFlag = IPCFlag(self.uuid) 

2148 for i in range(SR.LOCK_RETRY_ATTEMPTS_LOCK): 

2149 if self._srLock.acquireNoblock(): 

2150 self._locked += 1 

2151 return 

2152 if abortFlag.test(FLAG_TYPE_ABORT): 

2153 raise AbortException("Abort requested") 

2154 time.sleep(SR.LOCK_RETRY_INTERVAL) 

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

2156 

2157 self._locked += 1 

2158 

2159 def unlock(self): 

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

2161 return 

2162 assert(self._locked > 0) 

2163 self._locked -= 1 

2164 if self._locked == 0: 

2165 self._srLock.release() 

2166 

2167 def needUpdateBlockInfo(self): 

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

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

2170 continue 

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

2172 return True 

2173 return False 

2174 

2175 def updateBlockInfo(self): 

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

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

2178 continue 

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

2180 vdi.updateBlockInfo() 

2181 

2182 def cleanupCoalesceJournals(self): 

2183 """Remove stale coalesce VDI indicators""" 

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

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

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

2187 

2188 def cleanupJournals(self, dryRun=False): 

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

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

2191 entries = self.journaler.getAll(t) 

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

2193 if self.getVDI(uuid): 

2194 continue 

2195 if t == SR.JRN_CLONE: 

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

2197 if self.getVDI(baseUuid): 

2198 continue 

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

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

2201 if not dryRun: 

2202 self.journaler.remove(t, uuid) 

2203 

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

2205 return 0 

2206 

2207 def _coalesce(self, vdi): 

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

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

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

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

2212 else: 

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

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

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

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

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

2218 vdi._doCoalesce() 

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

2220 

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

2222 

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

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

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

2226 # scan 

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

2228 

2229 self.lock() 

2230 try: 

2231 vdi.parent._tagChildrenForRelink() 

2232 self.scan() 

2233 vdi._relinkSkip() 

2234 finally: 

2235 self.unlock() 

2236 # Reload the children to leave things consistent 

2237 vdi.parent._reloadChildren(vdi) 

2238 

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

2240 self.deleteVDI(vdi) 

2241 

2242 class CoalesceTracker: 

2243 GRACE_ITERATIONS = 1 

2244 MAX_ITERATIONS_NO_PROGRESS = 3 

2245 MAX_ITERATIONS = 10 

2246 MAX_INCREASE_FROM_MINIMUM = 1.2 

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

2248 " --> Final size {finSize}" 

2249 

2250 def __init__(self, sr): 

2251 self.itsNoProgress = 0 

2252 self.its = 0 

2253 self.minSize = float("inf") 

2254 self.history = [] 

2255 self.reason = "" 

2256 self.startSize = None 

2257 self.finishSize = None 

2258 self.sr = sr 

2259 

2260 def abortCoalesce(self, prevSize, curSize): 

2261 res = False 

2262 

2263 self.its += 1 

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

2265 initSize=prevSize, 

2266 finSize=curSize)) 

2267 

2268 self.finishSize = curSize 

2269 

2270 if self.startSize is None: 

2271 self.startSize = prevSize 

2272 

2273 if curSize < self.minSize: 

2274 self.minSize = curSize 

2275 

2276 if prevSize < self.minSize: 

2277 self.minSize = prevSize 

2278 

2279 if prevSize < curSize: 

2280 self.itsNoProgress += 1 

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

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

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

2284 

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

2286 max = self.MAX_ITERATIONS 

2287 self.reason = \ 

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

2289 res = True 

2290 

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

2292 self.MAX_ITERATIONS_NO_PROGRESS): 

2293 max = self.MAX_ITERATIONS_NO_PROGRESS 

2294 self.reason = \ 

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

2296 res = True 

2297 

2298 maxSizeFromMin = self.MAX_INCREASE_FROM_MINIMUM * self.minSize 

2299 if (self.its > self.GRACE_ITERATIONS and 

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

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

2302 " compared to minimum acheived" 

2303 res = True 

2304 

2305 return res 

2306 

2307 def printReasoning(self): 

2308 Util.log("Aborted coalesce") 

2309 for hist in self.history: 

2310 Util.log(hist) 

2311 Util.log(self.reason) 

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

2313 .format(size=self.startSize)) 

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

2315 .format(size=self.finishSize)) 

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

2317 .format(size=self.minSize)) 

2318 

2319 def _coalesceLeaf(self, vdi): 

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

2321 complete due to external changes, namely vdi_delete and vdi_snapshot 

2322 that alter leaf-coalescibility of vdi""" 

2323 tracker = self.CoalesceTracker(self) 

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

2325 prevSizeVHD = vdi.getSizeVHD() 

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

2327 return False 

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

2329 tracker.printReasoning() 

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

2331 .format(uuid=vdi.uuid)) 

2332 return self._liveLeafCoalesce(vdi) 

2333 

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

2335 speed = None 

2336 total_time = endTime - startTime 

2337 if total_time > 0: 

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

2339 return speed 

2340 

2341 def writeSpeedToFile(self, speed): 

2342 content = [] 

2343 speedFile = None 

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

2345 self.lock() 

2346 try: 

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

2348 lines = "" 

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

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

2351 else: 

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

2353 content = speedFile.readlines() 

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

2355 if len(content) > N_RUNNING_AVERAGE: 

2356 del content[0] 

2357 lines = "".join(content) 

2358 

2359 util.atomicFileWrite(path, VAR_RUN, lines) 

2360 finally: 

2361 if speedFile is not None: 

2362 speedFile.close() 

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

2364 self.unlock() 

2365 

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

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

2368 if speed is None: 

2369 return 

2370 

2371 self.writeSpeedToFile(speed) 

2372 

2373 def getStorageSpeed(self): 

2374 speedFile = None 

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

2376 self.lock() 

2377 try: 

2378 speed = None 

2379 if os.path.isfile(path): 

2380 speedFile = open(path) 

2381 content = speedFile.readlines() 

2382 try: 

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

2384 except ValueError: 

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

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

2387 return speed 

2388 

2389 if len(content): 

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

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

2392 # Defensive, should be impossible. 

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

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

2395 speed = None 

2396 else: 

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

2398 format(uuid=self.uuid)) 

2399 else: 

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

2401 format(uuid=self.uuid)) 

2402 return speed 

2403 finally: 

2404 if not (speedFile is None): 

2405 speedFile.close() 

2406 self.unlock() 

2407 

2408 def _snapshotCoalesce(self, vdi): 

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

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

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

2412 assert(AUTO_ONLINE_LEAF_COALESCE_ENABLED) 

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

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

2415 try: 

2416 ret = self.xapi.singleSnapshotVDI(vdi) 

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

2418 except XenAPI.Failure as e: 

2419 if util.isInvalidVDI(e): 

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

2421 return False 

2422 raise 

2423 self.scanLocked() 

2424 tempSnap = vdi.parent 

2425 if not tempSnap.isCoalesceable(): 

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

2427 return False 

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

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

2430 vhdSize = vdi.getSizeVHD() 

2431 self._coalesce(tempSnap) 

2432 if not vdi.isLeafCoalesceable(): 

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

2434 return False 

2435 return True 

2436 

2437 def _liveLeafCoalesce(self, vdi): 

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

2439 self.lock() 

2440 try: 

2441 self.scan() 

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

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

2444 return False 

2445 if not vdi.isLeafCoalesceable(): 

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

2447 return False 

2448 

2449 uuid = vdi.uuid 

2450 vdi.pause(failfast=True) 

2451 try: 

2452 try: 

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

2454 self._doCoalesceLeaf(vdi) 

2455 except: 

2456 Util.logException("_doCoalesceLeaf") 

2457 self._handleInterruptedCoalesceLeaf() 

2458 raise 

2459 finally: 

2460 vdi = self.getVDI(uuid) 

2461 if vdi: 

2462 vdi.ensureUnpaused() 

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

2464 if vdiOld: 

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

2466 self.deleteVDI(vdiOld) 

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

2468 finally: 

2469 self.cleanup() 

2470 self.unlock() 

2471 self.logFilter.logState() 

2472 return True 

2473 

2474 def _doCoalesceLeaf(self, vdi): 

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

2476 offline/atomic context""" 

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

2478 self._prepareCoalesceLeaf(vdi) 

2479 vdi.parent._setHidden(False) 

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

2481 vdi.validate(True) 

2482 vdi.parent.validate(True) 

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

2484 timeout = vdi.LIVE_LEAF_COALESCE_TIMEOUT 

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

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

2487 timeout = 0 

2488 vdi._coalesceVHD(timeout) 

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

2490 vdi.parent.validate(True) 

2491 #vdi._verifyContents(timeout / 2) 

2492 

2493 # rename 

2494 vdiUuid = vdi.uuid 

2495 oldName = vdi.fileName 

2496 origParentUuid = vdi.parent.uuid 

2497 vdi.rename(self.TMP_RENAME_PREFIX + vdiUuid) 

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

2499 vdi.parent.rename(vdiUuid) 

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

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

2502 

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

2504 # garbage 

2505 

2506 # update the VDI record 

2507 vdi.parent.delConfig(VDI.DB_VHD_PARENT) 

2508 if vdi.parent.raw: 

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

2510 vdi.parent.delConfig(VDI.DB_VHD_BLOCKS) 

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

2512 

2513 self._updateNode(vdi) 

2514 

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

2516 # minimize free space requirements) 

2517 parent = vdi.parent 

2518 vdi._setHidden(True) 

2519 vdi.parent.children = [] 

2520 vdi.parent = None 

2521 

2522 extraSpace = self._calcExtraSpaceNeeded(vdi, parent) 

2523 freeSpace = self.getFreeSpace() 

2524 if freeSpace < extraSpace: 

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

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

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

2528 self.deleteVDI(vdi) 

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

2530 

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

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

2533 

2534 self.forgetVDI(origParentUuid) 

2535 self._finishCoalesceLeaf(parent) 

2536 self._updateSlavesOnResize(parent) 

2537 

2538 def _calcExtraSpaceNeeded(self, child, parent): 

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

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

2541 if extra < 0: 

2542 extra = 0 

2543 return extra 

2544 

2545 def _prepareCoalesceLeaf(self, vdi): 

2546 pass 

2547 

2548 def _updateNode(self, vdi): 

2549 pass 

2550 

2551 def _finishCoalesceLeaf(self, parent): 

2552 pass 

2553 

2554 def _updateSlavesOnUndoLeafCoalesce(self, parent, child): 

2555 pass 

2556 

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

2558 pass 

2559 

2560 def _updateSlavesOnResize(self, vdi): 

2561 pass 

2562 

2563 def _removeStaleVDIs(self, uuidsPresent): 

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

2565 if not uuid in uuidsPresent: 

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

2567 self.vdis[uuid]) 

2568 del self.vdis[uuid] 

2569 

2570 def _handleInterruptedCoalesceLeaf(self): 

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

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

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

2574 finish the operation""" 

2575 # abstract 

2576 pass 

2577 

2578 def _buildTree(self, force): 

2579 self.vdiTrees = [] 

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

2581 if vdi.parentUuid: 

2582 parent = self.getVDI(vdi.parentUuid) 

2583 if not parent: 

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

2585 self.vdiTrees.append(vdi) 

2586 continue 

2587 if force: 

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

2589 (vdi.parentUuid, vdi.uuid)) 

2590 self.vdiTrees.append(vdi) 

2591 continue 

2592 else: 

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

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

2595 vdi.parent = parent 

2596 parent.children.append(vdi) 

2597 else: 

2598 self.vdiTrees.append(vdi) 

2599 

2600 

2601class FileSR(SR): 

2602 TYPE = SR.TYPE_FILE 

2603 CACHE_FILE_EXT = ".vhdcache" 

2604 # cache cleanup actions 

2605 CACHE_ACTION_KEEP = 0 

2606 CACHE_ACTION_REMOVE = 1 

2607 CACHE_ACTION_REMOVE_IF_INACTIVE = 2 

2608 

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

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

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

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

2613 

2614 def scan(self, force=False): 

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

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

2617 vhds = self._scan(force) 

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

2619 vdi = self.getVDI(uuid) 

2620 if not vdi: 

2621 self.logFilter.logNewVDI(uuid) 

2622 vdi = FileVDI(self, uuid, False) 

2623 self.vdis[uuid] = vdi 

2624 vdi.load(vhdInfo) 

2625 uuidsPresent = list(vhds.keys()) 

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

2627 for rawName in rawList: 

2628 uuid = FileVDI.extractUuid(rawName) 

2629 uuidsPresent.append(uuid) 

2630 vdi = self.getVDI(uuid) 

2631 if not vdi: 

2632 self.logFilter.logNewVDI(uuid) 

2633 vdi = FileVDI(self, uuid, True) 

2634 self.vdis[uuid] = vdi 

2635 self._removeStaleVDIs(uuidsPresent) 

2636 self._buildTree(force) 

2637 self.logFilter.logState() 

2638 self._handleInterruptedCoalesceLeaf() 

2639 

2640 def getFreeSpace(self): 

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

2642 

2643 def deleteVDIs(self, vdiList): 

2644 rootDeleted = False 

2645 for vdi in vdiList: 

2646 if not vdi.parent: 

2647 rootDeleted = True 

2648 break 

2649 SR.deleteVDIs(self, vdiList) 

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

2651 self.xapi.markCacheSRsDirty() 

2652 

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

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

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

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

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

2658 <maxAge> hours. 

2659 Return number of caches removed. 

2660 """ 

2661 numRemoved = 0 

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

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

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

2665 for cacheFile in cacheFiles: 

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

2667 action = self.CACHE_ACTION_KEEP 

2668 rec = self.xapi.getRecordVDI(uuid) 

2669 if not rec: 

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

2671 action = self.CACHE_ACTION_REMOVE 

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

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

2674 action = self.CACHE_ACTION_REMOVE 

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

2676 lastAccess = datetime.datetime.fromtimestamp( \ 

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

2678 if lastAccess < cutoff: 

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

2680 action = self.CACHE_ACTION_REMOVE_IF_INACTIVE 

2681 

2682 if action == self.CACHE_ACTION_KEEP: 

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

2684 continue 

2685 

2686 lockId = uuid 

2687 parentUuid = None 

2688 if rec and rec["managed"]: 

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

2690 if parentUuid: 

2691 lockId = parentUuid 

2692 

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

2694 cacheLock.acquire() 

2695 try: 

2696 if self._cleanupCache(uuid, action): 

2697 numRemoved += 1 

2698 finally: 

2699 cacheLock.release() 

2700 return numRemoved 

2701 

2702 def _cleanupCache(self, uuid, action): 

2703 assert(action != self.CACHE_ACTION_KEEP) 

2704 rec = self.xapi.getRecordVDI(uuid) 

2705 if rec and rec["allow_caching"]: 

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

2707 return False 

2708 

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

2710 tapdisk = blktap2.Tapdisk.find_by_path(fullPath) 

2711 if tapdisk: 

2712 if action == self.CACHE_ACTION_REMOVE_IF_INACTIVE: 

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

2714 return False 

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

2716 tapdisk.shutdown() 

2717 

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

2719 os.unlink(fullPath) 

2720 return True 

2721 

2722 def _isCacheFileName(self, name): 

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

2724 name.endswith(self.CACHE_FILE_EXT) 

2725 

2726 def _scan(self, force): 

2727 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2728 error = False 

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

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

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

2732 if vhdInfo.error: 

2733 error = True 

2734 break 

2735 if not error: 

2736 return vhds 

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

2738 if force: 

2739 return vhds 

2740 raise util.SMException("Scan error") 

2741 

2742 def deleteVDI(self, vdi): 

2743 self._checkSlaves(vdi) 

2744 SR.deleteVDI(self, vdi) 

2745 

2746 def _checkSlaves(self, vdi): 

2747 onlineHosts = self.xapi.getOnlineHosts() 

2748 abortFlag = IPCFlag(self.uuid) 

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

2750 hostRef = pbdRecord["host"] 

2751 if hostRef == self.xapi._hostRef: 

2752 continue 

2753 if abortFlag.test(FLAG_TYPE_ABORT): 

2754 raise AbortException("Aborting due to signal") 

2755 try: 

2756 self._checkSlave(hostRef, vdi) 

2757 except util.CommandException: 

2758 if hostRef in onlineHosts: 

2759 raise 

2760 

2761 def _checkSlave(self, hostRef, vdi): 

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

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

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

2765 text = _host.call_plugin( * call) 

2766 

2767 def _handleInterruptedCoalesceLeaf(self): 

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

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

2770 fileList = os.listdir(self.path) 

2771 childName = uuid + vhdutil.FILE_EXTN_VHD 

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

2773 parentName1 = parentUuid + vhdutil.FILE_EXTN_VHD 

2774 parentName2 = parentUuid + vhdutil.FILE_EXTN_RAW 

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

2776 if parentPresent or tmpChildName in fileList: 

2777 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

2778 else: 

2779 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

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

2781 vdi = self.getVDI(uuid) 

2782 if vdi: 

2783 vdi.ensureUnpaused() 

2784 

2785 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

2787 parent = self.getVDI(parentUuid) 

2788 if not parent: 

2789 parent = self.getVDI(childUuid) 

2790 if not parent: 

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

2792 (parentUuid, childUuid)) 

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

2794 parent.rename(parentUuid) 

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

2796 

2797 child = self.getVDI(childUuid) 

2798 if not child: 

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

2800 if not child: 

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

2802 (childUuid, self.TMP_RENAME_PREFIX + childUuid)) 

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

2804 child.rename(childUuid) 

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

2806 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

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

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

2809 

2810 if child.hidden: 

2811 child._setHidden(False) 

2812 if not parent.hidden: 

2813 parent._setHidden(True) 

2814 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

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

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

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

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

2819 

2820 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

2822 vdi = self.getVDI(childUuid) 

2823 if not vdi: 

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

2825 try: 

2826 self.forgetVDI(parentUuid) 

2827 except XenAPI.Failure: 

2828 pass 

2829 self._updateSlavesOnResize(vdi) 

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

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

2832 

2833 

2834class LVHDSR(SR): 

2835 TYPE = SR.TYPE_LVHD 

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

2837 

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

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

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

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

2842 

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

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

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

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

2847 

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

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

2850 

2851 def deleteVDI(self, vdi): 

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

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

2854 self._checkSlaves(vdi) 

2855 SR.deleteVDI(self, vdi) 

2856 

2857 def forgetVDI(self, vdiUuid): 

2858 SR.forgetVDI(self, vdiUuid) 

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

2860 LVMMetadataHandler(mdpath).deleteVdiFromMetadata(vdiUuid) 

2861 

2862 def getFreeSpace(self): 

2863 stats = lvutil._getVGstats(self.vgName) 

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

2865 

2866 def cleanup(self): 

2867 if not self.lvActivator.deactivateAll(): 

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

2869 

2870 def needUpdateBlockInfo(self): 

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

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

2873 continue 

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

2875 return True 

2876 return False 

2877 

2878 def updateBlockInfo(self): 

2879 numUpdated = 0 

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

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

2882 continue 

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

2884 vdi.updateBlockInfo() 

2885 numUpdated += 1 

2886 if numUpdated: 

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

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

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

2890 # inherit the refcount value and preventing the correct decrement 

2891 self.cleanup() 

2892 

2893 def scan(self, force=False): 

2894 vdis = self._scan(force) 

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

2896 vdi = self.getVDI(uuid) 

2897 if not vdi: 

2898 self.logFilter.logNewVDI(uuid) 

2899 vdi = LVHDVDI(self, uuid, 

2900 vdiInfo.vdiType == vhdutil.VDI_TYPE_RAW) 

2901 self.vdis[uuid] = vdi 

2902 vdi.load(vdiInfo) 

2903 self._removeStaleVDIs(vdis.keys()) 

2904 self._buildTree(force) 

2905 self.logFilter.logState() 

2906 self._handleInterruptedCoalesceLeaf() 

2907 

2908 def _scan(self, force): 

2909 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

2910 error = False 

2911 self.lvmCache.refresh() 

2912 vdis = lvhdutil.getVDIInfo(self.lvmCache) 

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

2914 if vdiInfo.scanError: 

2915 error = True 

2916 break 

2917 if not error: 

2918 return vdis 

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

2920 if force: 

2921 return vdis 

2922 raise util.SMException("Scan error") 

2923 

2924 def _removeStaleVDIs(self, uuidsPresent): 

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

2926 if not uuid in uuidsPresent: 

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

2928 self.vdis[uuid]) 

2929 del self.vdis[uuid] 

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

2931 self.lvActivator.remove(uuid, False) 

2932 

2933 def _liveLeafCoalesce(self, vdi): 

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

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

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

2937 to avoid a protracted downtime""" 

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

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

2940 vdi.parent._increaseSizeVirt(vdi.sizeVirt) 

2941 

2942 return SR._liveLeafCoalesce(self, vdi) 

2943 

2944 def _prepareCoalesceLeaf(self, vdi): 

2945 vdi._activateChain() 

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

2947 vdi.deflate() 

2948 vdi.inflateParentForCoalesce() 

2949 

2950 def _updateNode(self, vdi): 

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

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

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

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

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

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

2957 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

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

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

2960 pCnt = pCnt - cBcnt 

2961 assert(pCnt >= 0) 

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

2963 

2964 def _finishCoalesceLeaf(self, parent): 

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

2966 parent.inflateFully() 

2967 else: 

2968 parent.deflate() 

2969 

2970 def _calcExtraSpaceNeeded(self, child, parent): 

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

2972 

2973 def _handleInterruptedCoalesceLeaf(self): 

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

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

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

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

2978 self.TMP_RENAME_PREFIX + uuid 

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

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

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

2982 self.lvmCache.checkLV(parentLV2)) 

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

2984 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

2985 else: 

2986 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

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

2988 vdi = self.getVDI(uuid) 

2989 if vdi: 

2990 vdi.ensureUnpaused() 

2991 

2992 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

2994 parent = self.getVDI(parentUuid) 

2995 if not parent: 

2996 parent = self.getVDI(childUuid) 

2997 if not parent: 

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

2999 (parentUuid, childUuid)) 

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

3001 parent.rename(parentUuid) 

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

3003 

3004 child = self.getVDI(childUuid) 

3005 if not child: 

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

3007 if not child: 

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

3009 (childUuid, self.TMP_RENAME_PREFIX + childUuid)) 

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

3011 child.rename(childUuid) 

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

3013 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

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

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

3016 

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

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

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

3020 ns = lvhdutil.NS_PREFIX_LVM + self.uuid 

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

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

3023 pCnt = pCnt + cBcnt 

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

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

3026 

3027 parent.deflate() 

3028 child.inflateFully() 

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

3030 if child.hidden: 

3031 child._setHidden(False) 

3032 if not parent.hidden: 

3033 parent._setHidden(True) 

3034 if not parent.lvReadonly: 

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

3036 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

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

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

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

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

3041 

3042 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

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

3044 vdi = self.getVDI(childUuid) 

3045 if not vdi: 

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

3047 vdi.inflateFully() 

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

3049 try: 

3050 self.forgetVDI(parentUuid) 

3051 except XenAPI.Failure: 

3052 pass 

3053 self._updateSlavesOnResize(vdi) 

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

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

3056 

3057 def _checkSlaves(self, vdi): 

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

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

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

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

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

3063 "action1": "deactivateNoRefcount", 

3064 "lvName1": vdi.fileName, 

3065 "action2": "cleanupLockAndRefcount", 

3066 "uuid2": vdi.uuid, 

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

3068 onlineHosts = self.xapi.getOnlineHosts() 

3069 abortFlag = IPCFlag(self.uuid) 

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

3071 hostRef = pbdRecord["host"] 

3072 if hostRef == self.xapi._hostRef: 

3073 continue 

3074 if abortFlag.test(FLAG_TYPE_ABORT): 

3075 raise AbortException("Aborting due to signal") 

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

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

3078 try: 

3079 self.xapi.ensureInactive(hostRef, args) 

3080 except XenAPI.Failure: 

3081 if hostRef in onlineHosts: 

3082 raise 

3083 

3084 def _updateSlavesOnUndoLeafCoalesce(self, parent, child): 

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

3086 if not slaves: 

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

3088 child) 

3089 return 

3090 

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

3092 self.TMP_RENAME_PREFIX + child.uuid 

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

3094 "action1": "deactivateNoRefcount", 

3095 "lvName1": tmpName, 

3096 "action2": "deactivateNoRefcount", 

3097 "lvName2": child.fileName, 

3098 "action3": "refresh", 

3099 "lvName3": child.fileName, 

3100 "action4": "refresh", 

3101 "lvName4": parent.fileName} 

3102 for slave in slaves: 

3103 Util.log("Updating %s, %s, %s on slave %s" % \ 

3104 (tmpName, child.fileName, parent.fileName, 

3105 self.xapi.getRecordHost(slave)['hostname'])) 

3106 text = self.xapi.session.xenapi.host.call_plugin( \ 

3107 slave, self.xapi.PLUGIN_ON_SLAVE, "multi", args) 

3108 Util.log("call-plugin returned: '%s'" % text) 

3109 

3110 def _updateSlavesOnRename(self, vdi, oldNameLV, origParentUuid): 

3111 slaves = util.get_slaves_attached_on(self.xapi.session, [vdi.uuid]) 

3112 if not slaves: 

3113 Util.log("Update-on-rename: VDI %s not attached on any slave" % vdi) 

3114 return 

3115 

3116 args = {"vgName": self.vgName, 

3117 "action1": "deactivateNoRefcount", 

3118 "lvName1": oldNameLV, 

3119 "action2": "refresh", 

3120 "lvName2": vdi.fileName, 

3121 "action3": "cleanupLockAndRefcount", 

3122 "uuid3": origParentUuid, 

3123 "ns3": lvhdutil.NS_PREFIX_LVM + self.uuid} 

3124 for slave in slaves: 

3125 Util.log("Updating %s to %s on slave %s" % \ 

3126 (oldNameLV, vdi.fileName, 

3127 self.xapi.getRecordHost(slave)['hostname'])) 

3128 text = self.xapi.session.xenapi.host.call_plugin( \ 

3129 slave, self.xapi.PLUGIN_ON_SLAVE, "multi", args) 

3130 Util.log("call-plugin returned: '%s'" % text) 

3131 

3132 def _updateSlavesOnResize(self, vdi): 

3133 uuids = [x.uuid for x in vdi.getAllLeaves()] 

3134 slaves = util.get_slaves_attached_on(self.xapi.session, uuids) 

3135 if not slaves: 

3136 util.SMlog("Update-on-resize: %s not attached on any slave" % vdi) 

3137 return 

3138 lvhdutil.lvRefreshOnSlaves(self.xapi.session, self.uuid, self.vgName, 

3139 vdi.fileName, vdi.uuid, slaves) 

3140 

3141 

3142class LinstorSR(SR): 

3143 TYPE = SR.TYPE_LINSTOR 

3144 

3145 def __init__(self, uuid, xapi, createLock, force): 

3146 if not LINSTOR_AVAILABLE: 

3147 raise util.SMException( 

3148 'Can\'t load cleanup LinstorSR: LINSTOR libraries are missing' 

3149 ) 

3150 

3151 SR.__init__(self, uuid, xapi, createLock, force) 

3152 self.path = LinstorVolumeManager.DEV_ROOT_PATH 

3153 self._reloadLinstor() 

3154 

3155 def deleteVDI(self, vdi): 

3156 self._checkSlaves(vdi) 

3157 SR.deleteVDI(self, vdi) 

3158 

3159 def getFreeSpace(self): 

3160 return self._linstor.max_volume_size_allowed 

3161 

3162 def scan(self, force=False): 

3163 all_vdi_info = self._scan(force) 

3164 for uuid, vdiInfo in all_vdi_info.items(): 

3165 # When vdiInfo is None, the VDI is RAW. 

3166 vdi = self.getVDI(uuid) 

3167 if not vdi: 

3168 self.logFilter.logNewVDI(uuid) 

3169 vdi = LinstorVDI(self, uuid, not vdiInfo) 

3170 self.vdis[uuid] = vdi 

3171 if vdiInfo: 

3172 vdi.load(vdiInfo) 

3173 self._removeStaleVDIs(all_vdi_info.keys()) 

3174 self._buildTree(force) 

3175 self.logFilter.logState() 

3176 self._handleInterruptedCoalesceLeaf() 

3177 

3178 def pauseVDIs(self, vdiList): 

3179 self._linstor.ensure_volume_list_is_not_locked( 

3180 vdiList, timeout=LinstorVDI.VOLUME_LOCK_TIMEOUT 

3181 ) 

3182 return super(LinstorSR, self).pauseVDIs(vdiList) 

3183 

3184 def _reloadLinstor(self): 

3185 session = self.xapi.session 

3186 host_ref = util.get_this_host_ref(session) 

3187 sr_ref = session.xenapi.SR.get_by_uuid(self.uuid) 

3188 

3189 pbd = util.find_my_pbd(session, host_ref, sr_ref) 

3190 if pbd is None: 

3191 raise util.SMException('Failed to find PBD') 

3192 

3193 dconf = session.xenapi.PBD.get_device_config(pbd) 

3194 group_name = dconf['group-name'] 

3195 

3196 controller_uri = get_controller_uri() 

3197 self.journaler = LinstorJournaler( 

3198 controller_uri, group_name, logger=util.SMlog 

3199 ) 

3200 

3201 self._linstor = LinstorVolumeManager( 

3202 controller_uri, 

3203 group_name, 

3204 repair=True, 

3205 logger=util.SMlog 

3206 ) 

3207 self._vhdutil = LinstorVhdUtil(session, self._linstor) 

3208 

3209 def _scan(self, force): 

3210 for i in range(SR.SCAN_RETRY_ATTEMPTS): 

3211 self._reloadLinstor() 

3212 error = False 

3213 try: 

3214 all_vdi_info = self._load_vdi_info() 

3215 for uuid, vdiInfo in all_vdi_info.items(): 

3216 if vdiInfo and vdiInfo.error: 

3217 error = True 

3218 break 

3219 if not error: 

3220 return all_vdi_info 

3221 Util.log('Scan error, retrying ({})'.format(i)) 

3222 except Exception as e: 

3223 Util.log('Scan exception, retrying ({}): {}'.format(i, e)) 

3224 Util.log(traceback.format_exc()) 

3225 

3226 if force: 

3227 return all_vdi_info 

3228 raise util.SMException('Scan error') 

3229 

3230 def _load_vdi_info(self): 

3231 all_vdi_info = {} 

3232 

3233 # TODO: Ensure metadata contains the right info. 

3234 

3235 all_volume_info = self._linstor.get_volumes_with_info() 

3236 volumes_metadata = self._linstor.get_volumes_with_metadata() 

3237 for vdi_uuid, volume_info in all_volume_info.items(): 

3238 try: 

3239 volume_metadata = volumes_metadata[vdi_uuid] 

3240 if not volume_info.name and not list(volume_metadata.items()): 

3241 continue # Ignore it, probably deleted. 

3242 

3243 if vdi_uuid.startswith('DELETED_'): 

3244 # Assume it's really a RAW volume of a failed snap without VHD header/footer. 

3245 # We must remove this VDI now without adding it in the VDI list. 

3246 # Otherwise `Relinking` calls and other actions can be launched on it. 

3247 # We don't want that... 

3248 Util.log('Deleting bad VDI {}'.format(vdi_uuid)) 

3249 

3250 self.lock() 

3251 try: 

3252 self._linstor.destroy_volume(vdi_uuid) 

3253 try: 

3254 self.forgetVDI(vdi_uuid) 

3255 except: 

3256 pass 

3257 except Exception as e: 

3258 Util.log('Cannot delete bad VDI: {}'.format(e)) 

3259 finally: 

3260 self.unlock() 

3261 continue 

3262 

3263 vdi_type = volume_metadata.get(VDI_TYPE_TAG) 

3264 volume_name = self._linstor.get_volume_name(vdi_uuid) 

3265 if volume_name.startswith(LINSTOR_PERSISTENT_PREFIX): 

3266 # Always RAW! 

3267 info = None 

3268 elif vdi_type == vhdutil.VDI_TYPE_VHD: 

3269 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3270 else: 

3271 # Ensure it's not a VHD... 

3272 try: 

3273 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3274 except: 

3275 try: 

3276 self._vhdutil.force_repair( 

3277 self._linstor.get_device_path(vdi_uuid) 

3278 ) 

3279 info = self._vhdutil.get_vhd_info(vdi_uuid) 

3280 except: 

3281 info = None 

3282 

3283 except Exception as e: 

3284 Util.log( 

3285 ' [VDI {}: failed to load VDI info]: {}' 

3286 .format(vdi_uuid, e) 

3287 ) 

3288 info = vhdutil.VHDInfo(vdi_uuid) 

3289 info.error = 1 

3290 

3291 all_vdi_info[vdi_uuid] = info 

3292 

3293 return all_vdi_info 

3294 

3295 def _prepareCoalesceLeaf(self, vdi): 

3296 vdi._activateChain() 

3297 vdi.deflate() 

3298 vdi._inflateParentForCoalesce() 

3299 

3300 def _finishCoalesceLeaf(self, parent): 

3301 if not parent.isSnapshot() or parent.isAttachedRW(): 

3302 parent.inflateFully() 

3303 else: 

3304 parent.deflate() 

3305 

3306 def _calcExtraSpaceNeeded(self, child, parent): 

3307 return LinstorVhdUtil.compute_volume_size(parent.sizeVirt, parent.vdi_type) - parent.getDrbdSize() 

3308 

3309 def _hasValidDevicePath(self, uuid): 

3310 try: 

3311 self._linstor.get_device_path(uuid) 

3312 except Exception: 

3313 # TODO: Maybe log exception. 

3314 return False 

3315 return True 

3316 

3317 def _liveLeafCoalesce(self, vdi): 

3318 self.lock() 

3319 try: 

3320 self._linstor.ensure_volume_is_not_locked( 

3321 vdi.uuid, timeout=LinstorVDI.VOLUME_LOCK_TIMEOUT 

3322 ) 

3323 return super(LinstorSR, self)._liveLeafCoalesce(vdi) 

3324 finally: 

3325 self.unlock() 

3326 

3327 def _handleInterruptedCoalesceLeaf(self): 

3328 entries = self.journaler.get_all(VDI.JRN_LEAF) 

3329 for uuid, parentUuid in entries.items(): 

3330 if self._hasValidDevicePath(parentUuid) or \ 

3331 self._hasValidDevicePath(self.TMP_RENAME_PREFIX + uuid): 

3332 self._undoInterruptedCoalesceLeaf(uuid, parentUuid) 

3333 else: 

3334 self._finishInterruptedCoalesceLeaf(uuid, parentUuid) 

3335 self.journaler.remove(VDI.JRN_LEAF, uuid) 

3336 vdi = self.getVDI(uuid) 

3337 if vdi: 

3338 vdi.ensureUnpaused() 

3339 

3340 def _undoInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

3341 Util.log('*** UNDO LEAF-COALESCE') 

3342 parent = self.getVDI(parentUuid) 

3343 if not parent: 

3344 parent = self.getVDI(childUuid) 

3345 if not parent: 

3346 raise util.SMException( 

3347 'Neither {} nor {} found'.format(parentUuid, childUuid) 

3348 ) 

3349 Util.log( 

3350 'Renaming parent back: {} -> {}'.format(childUuid, parentUuid) 

3351 ) 

3352 parent.rename(parentUuid) 

3353 

3354 child = self.getVDI(childUuid) 

3355 if not child: 

3356 child = self.getVDI(self.TMP_RENAME_PREFIX + childUuid) 

3357 if not child: 

3358 raise util.SMException( 

3359 'Neither {} nor {} found'.format( 

3360 childUuid, self.TMP_RENAME_PREFIX + childUuid 

3361 ) 

3362 ) 

3363 Util.log('Renaming child back to {}'.format(childUuid)) 

3364 child.rename(childUuid) 

3365 Util.log('Updating the VDI record') 

3366 child.setConfig(VDI.DB_VHD_PARENT, parentUuid) 

3367 child.setConfig(VDI.DB_VDI_TYPE, vhdutil.VDI_TYPE_VHD) 

3368 

3369 # TODO: Maybe deflate here. 

3370 

3371 if child.hidden: 

3372 child._setHidden(False) 

3373 if not parent.hidden: 

3374 parent._setHidden(True) 

3375 self._updateSlavesOnUndoLeafCoalesce(parent, child) 

3376 Util.log('*** leaf-coalesce undo successful') 

3377 

3378 def _finishInterruptedCoalesceLeaf(self, childUuid, parentUuid): 

3379 Util.log('*** FINISH LEAF-COALESCE') 

3380 vdi = self.getVDI(childUuid) 

3381 if not vdi: 

3382 raise util.SMException('VDI {} not found'.format(childUuid)) 

3383 # TODO: Maybe inflate. 

3384 try: 

3385 self.forgetVDI(parentUuid) 

3386 except XenAPI.Failure: 

3387 pass 

3388 self._updateSlavesOnResize(vdi) 

3389 Util.log('*** finished leaf-coalesce successfully') 

3390 

3391 def _checkSlaves(self, vdi): 

3392 try: 

3393 all_openers = self._linstor.get_volume_openers(vdi.uuid) 

3394 for openers in all_openers.values(): 

3395 for opener in openers.values(): 

3396 if opener['process-name'] != 'tapdisk': 

3397 raise util.SMException( 

3398 'VDI {} is in use: {}'.format(vdi.uuid, all_openers) 

3399 ) 

3400 except LinstorVolumeManagerError as e: 

3401 if e.code != LinstorVolumeManagerError.ERR_VOLUME_NOT_EXISTS: 

3402 raise 

3403 

3404 

3405################################################################################ 

3406# 

3407# Helpers 

3408# 

3409def daemonize(): 

3410 pid = os.fork() 

3411 if pid: 

3412 os.waitpid(pid, 0) 

3413 Util.log("New PID [%d]" % pid) 

3414 return False 

3415 os.chdir("/") 

3416 os.setsid() 

3417 pid = os.fork() 

3418 if pid: 

3419 Util.log("Will finish as PID [%d]" % pid) 

3420 os._exit(0) 

3421 for fd in [0, 1, 2]: 

3422 try: 

3423 os.close(fd) 

3424 except OSError: 

3425 pass 

3426 # we need to fill those special fd numbers or pread won't work 

3427 sys.stdin = open("/dev/null", 'r') 

3428 sys.stderr = open("/dev/null", 'w') 

3429 sys.stdout = open("/dev/null", 'w') 

3430 # As we're a new process we need to clear the lock objects 

3431 lock.Lock.clearAll() 

3432 return True 

3433 

3434 

3435def normalizeType(type): 

3436 if type in LVHDSR.SUBTYPES: 

3437 type = SR.TYPE_LVHD 

3438 if type in ["lvm", "lvmoiscsi", "lvmohba", "lvmofcoe"]: 

3439 # temporary while LVHD is symlinked as LVM 

3440 type = SR.TYPE_LVHD 

3441 if type in [ 

3442 "ext", "nfs", "ocfsoiscsi", "ocfsohba", "smb", "cephfs", "glusterfs", 

3443 "moosefs", "xfs", "zfs", "largeblock" 

3444 ]: 

3445 type = SR.TYPE_FILE 

3446 if type in ["linstor"]: 

3447 type = SR.TYPE_LINSTOR 

3448 if type not in SR.TYPES: 

3449 raise util.SMException("Unsupported SR type: %s" % type) 

3450 return type 

3451 

3452GCPAUSE_DEFAULT_SLEEP = 5 * 60 

3453 

3454 

3455def _gc_init_file(sr_uuid): 

3456 return os.path.join(NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init') 

3457 

3458 

3459def _create_init_file(sr_uuid): 

3460 util.makedirs(os.path.join(NON_PERSISTENT_DIR, str(sr_uuid))) 

3461 with open(os.path.join( 

3462 NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init'), 'w+') as f: 

3463 f.write('1') 

3464 

3465 

3466def _gcLoopPause(sr, dryRun=False, immediate=False): 

3467 if immediate: 

3468 return 

3469 

3470 # Check to see if the GCPAUSE_FISTPOINT is present. If so the fist 

3471 # point will just return. Otherwise, fall back on an abortable sleep. 

3472 

3473 if util.fistpoint.is_active(util.GCPAUSE_FISTPOINT): 

3474 

3475 util.fistpoint.activate_custom_fn(util.GCPAUSE_FISTPOINT, 3475 ↛ exitline 3475 didn't jump to the function exit

3476 lambda *args: None) 

3477 elif os.path.exists(_gc_init_file(sr.uuid)): 

3478 def abortTest(): 

3479 return IPCFlag(sr.uuid).test(FLAG_TYPE_ABORT) 

3480 

3481 # If time.sleep hangs we are in deep trouble, however for 

3482 # completeness we set the timeout of the abort thread to 

3483 # 110% of GCPAUSE_DEFAULT_SLEEP. 

3484 Util.log("GC active, about to go quiet") 

3485 Util.runAbortable(lambda: time.sleep(GCPAUSE_DEFAULT_SLEEP), 3485 ↛ exitline 3485 didn't run the lambda on line 3485

3486 None, sr.uuid, abortTest, VDI.POLL_INTERVAL, 

3487 GCPAUSE_DEFAULT_SLEEP * 1.1) 

3488 Util.log("GC active, quiet period ended") 

3489 

3490 

3491def _gcLoop(sr, dryRun=False, immediate=False): 

3492 if not lockGCActive.acquireNoblock(): 3492 ↛ 3493line 3492 didn't jump to line 3493, because the condition on line 3492 was never true

3493 Util.log("Another GC instance already active, exiting") 

3494 return 

3495 

3496 # Check we're still attached after acquiring locks 

3497 if not sr.xapi.isPluggedHere(): 

3498 Util.log("SR no longer attached, exiting") 

3499 return 

3500 

3501 # Clean up Intellicache files 

3502 sr.cleanupCache() 

3503 

3504 # Track how many we do 

3505 coalesced = 0 

3506 task_status = "success" 

3507 try: 

3508 # Check if any work needs to be done 

3509 if not sr.xapi.isPluggedHere(): 3509 ↛ 3510line 3509 didn't jump to line 3510, because the condition on line 3509 was never true

3510 Util.log("SR no longer attached, exiting") 

3511 return 

3512 sr.scanLocked() 

3513 if not sr.hasWork(): 

3514 Util.log("No work, exiting") 

3515 return 

3516 sr.xapi.create_task( 

3517 "Garbage Collection", 

3518 "Garbage collection for SR %s" % sr.uuid) 

3519 _gcLoopPause(sr, dryRun, immediate=immediate) 

3520 while True: 

3521 if not sr.xapi.isPluggedHere(): 3521 ↛ 3522line 3521 didn't jump to line 3522, because the condition on line 3521 was never true

3522 Util.log("SR no longer attached, exiting") 

3523 break 

3524 sr.scanLocked() 

3525 if not sr.hasWork(): 

3526 Util.log("No work, exiting") 

3527 break 

3528 

3529 if not lockGCRunning.acquireNoblock(): 3529 ↛ 3530line 3529 didn't jump to line 3530, because the condition on line 3529 was never true

3530 Util.log("Unable to acquire GC running lock.") 

3531 return 

3532 try: 

3533 if not sr.gcEnabled(): 3533 ↛ 3534line 3533 didn't jump to line 3534, because the condition on line 3533 was never true

3534 break 

3535 

3536 sr.xapi.update_task_progress("done", coalesced) 

3537 

3538 sr.cleanupCoalesceJournals() 

3539 # Create the init file here in case startup is waiting on it 

3540 _create_init_file(sr.uuid) 

3541 sr.scanLocked() 

3542 sr.updateBlockInfo() 

3543 

3544 howmany = len(sr.findGarbage()) 

3545 if howmany > 0: 

3546 Util.log("Found %d orphaned vdis" % howmany) 

3547 sr.lock() 

3548 try: 

3549 sr.garbageCollect(dryRun) 

3550 finally: 

3551 sr.unlock() 

3552 sr.xapi.srUpdate() 

3553 

3554 candidate = sr.findCoalesceable() 

3555 if candidate: 

3556 util.fistpoint.activate( 

3557 "LVHDRT_finding_a_suitable_pair", sr.uuid) 

3558 sr.coalesce(candidate, dryRun) 

3559 sr.xapi.srUpdate() 

3560 coalesced += 1 

3561 continue 

3562 

3563 candidate = sr.findLeafCoalesceable() 

3564 if candidate: 3564 ↛ 3571line 3564 didn't jump to line 3571, because the condition on line 3564 was never false

3565 sr.coalesceLeaf(candidate, dryRun) 

3566 sr.xapi.srUpdate() 

3567 coalesced += 1 

3568 continue 

3569 

3570 finally: 

3571 lockGCRunning.release() 3571 ↛ 3576line 3571 didn't jump to line 3576, because the break on line 3534 wasn't executed

3572 except: 

3573 task_status = "failure" 

3574 raise 

3575 finally: 

3576 sr.xapi.set_task_status(task_status) 

3577 Util.log("GC process exiting, no work left") 

3578 _create_init_file(sr.uuid) 

3579 lockGCActive.release() 

3580 

3581 

3582def _xapi_enabled(session, hostref): 

3583 host = session.xenapi.host.get_record(hostref) 

3584 return host['enabled'] 

3585 

3586 

3587def _ensure_xapi_initialised(session): 

3588 """ 

3589 Don't want to start GC until Xapi is fully initialised 

3590 """ 

3591 local_session = None 

3592 if session is None: 

3593 local_session = util.get_localAPI_session() 

3594 session = local_session 

3595 

3596 try: 

3597 hostref = session.xenapi.host.get_by_uuid(util.get_this_host()) 

3598 while not _xapi_enabled(session, hostref): 

3599 util.SMlog("Xapi not ready, GC waiting") 

3600 time.sleep(15) 

3601 finally: 

3602 if local_session is not None: 

3603 local_session.xenapi.session.logout() 

3604 

3605def _gc(session, srUuid, dryRun=False, immediate=False): 

3606 init(srUuid) 

3607 _ensure_xapi_initialised(session) 

3608 sr = SR.getInstance(srUuid, session) 

3609 if not sr.gcEnabled(False): 3609 ↛ 3610line 3609 didn't jump to line 3610, because the condition on line 3609 was never true

3610 return 

3611 

3612 try: 

3613 _gcLoop(sr, dryRun, immediate=immediate) 

3614 finally: 

3615 sr.cleanup() 

3616 sr.logFilter.logState() 

3617 del sr.xapi 

3618 

3619 

3620def _abort(srUuid, soft=False): 

3621 """Aborts an GC/coalesce. 

3622 

3623 srUuid: the UUID of the SR whose GC/coalesce must be aborted 

3624 soft: If set to True and there is a pending abort signal, the function 

3625 doesn't do anything. If set to False, a new abort signal is issued. 

3626 

3627 returns: If soft is set to False, we return True holding lockGCActive. If 

3628 soft is set to False and an abort signal is pending, we return False 

3629 without holding lockGCActive. An exception is raised in case of error.""" 

3630 Util.log("=== SR %s: abort ===" % (srUuid)) 

3631 init(srUuid) 

3632 if not lockGCActive.acquireNoblock(): 

3633 gotLock = False 

3634 Util.log("Aborting currently-running instance (SR %s)" % srUuid) 

3635 abortFlag = IPCFlag(srUuid) 

3636 if not abortFlag.set(FLAG_TYPE_ABORT, soft): 

3637 return False 

3638 for i in range(SR.LOCK_RETRY_ATTEMPTS): 

3639 gotLock = lockGCActive.acquireNoblock() 

3640 if gotLock: 

3641 break 

3642 time.sleep(SR.LOCK_RETRY_INTERVAL) 

3643 abortFlag.clear(FLAG_TYPE_ABORT) 

3644 if not gotLock: 

3645 raise util.CommandException(code=errno.ETIMEDOUT, 

3646 reason="SR %s: error aborting existing process" % srUuid) 

3647 return True 

3648 

3649 

3650def init(srUuid): 

3651 global lockGCRunning 

3652 if not lockGCRunning: 3652 ↛ 3653line 3652 didn't jump to line 3653, because the condition on line 3652 was never true

3653 lockGCRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, srUuid) 

3654 global lockGCActive 

3655 if not lockGCActive: 3655 ↛ 3656line 3655 didn't jump to line 3656, because the condition on line 3655 was never true

3656 lockGCActive = LockActive(srUuid) 

3657 

3658 

3659class LockActive: 

3660 """ 

3661 Wraps the use of LOCK_TYPE_GC_ACTIVE such that the lock cannot be acquired 

3662 if another process holds the SR lock. 

3663 """ 

3664 def __init__(self, srUuid): 

3665 self._lock = lock.Lock(LOCK_TYPE_GC_ACTIVE, srUuid) 

3666 self._srLock = lock.Lock(vhdutil.LOCK_TYPE_SR, srUuid) 

3667 

3668 def acquireNoblock(self): 

3669 self._srLock.acquire() 

3670 

3671 try: 

3672 return self._lock.acquireNoblock() 

3673 finally: 

3674 self._srLock.release() 

3675 

3676 def release(self): 

3677 self._lock.release() 

3678 

3679 

3680def usage(): 

3681 output = """Garbage collect and/or coalesce VHDs in a VHD-based SR 

3682 

3683Parameters: 

3684 -u --uuid UUID SR UUID 

3685 and one of: 

3686 -g --gc garbage collect, coalesce, and repeat while there is work 

3687 -G --gc_force garbage collect once, aborting any current operations 

3688 -c --cache-clean <max_age> clean up IntelliCache cache files older than 

3689 max_age hours 

3690 -a --abort abort any currently running operation (GC or coalesce) 

3691 -q --query query the current state (GC'ing, coalescing or not running) 

3692 -x --disable disable GC/coalesce (will be in effect until you exit) 

3693 -t --debug see Debug below 

3694 

3695Options: 

3696 -b --background run in background (return immediately) (valid for -g only) 

3697 -f --force continue in the presence of VHDs with errors (when doing 

3698 GC, this might cause removal of any such VHDs) (only valid 

3699 for -G) (DANGEROUS) 

3700 

3701Debug: 

3702 The --debug parameter enables manipulation of LVHD VDIs for debugging 

3703 purposes. ** NEVER USE IT ON A LIVE VM ** 

3704 The following parameters are required: 

3705 -t --debug <cmd> <cmd> is one of "activate", "deactivate", "inflate", 

3706 "deflate". 

3707 -v --vdi_uuid VDI UUID 

3708 """ 

3709 #-d --dry-run don't actually perform any SR-modifying operations 

3710 print(output) 

3711 Util.log("(Invalid usage)") 

3712 sys.exit(1) 

3713 

3714 

3715############################################################################## 

3716# 

3717# API 

3718# 

3719def abort(srUuid, soft=False): 

3720 """Abort GC/coalesce if we are currently GC'ing or coalescing a VDI pair. 

3721 """ 

3722 if _abort(srUuid, soft): 

3723 Util.log("abort: releasing the process lock") 

3724 lockGCActive.release() 

3725 return True 

3726 else: 

3727 return False 

3728 

3729 

3730def gc(session, srUuid, inBackground, dryRun=False): 

3731 """Garbage collect all deleted VDIs in SR "srUuid". Fork & return 

3732 immediately if inBackground=True. 

3733 

3734 The following algorithm is used: 

3735 1. If we are already GC'ing in this SR, return 

3736 2. If we are already coalescing a VDI pair: 

3737 a. Scan the SR and determine if the VDI pair is GC'able 

3738 b. If the pair is not GC'able, return 

3739 c. If the pair is GC'able, abort coalesce 

3740 3. Scan the SR 

3741 4. If there is nothing to collect, nor to coalesce, return 

3742 5. If there is something to collect, GC all, then goto 3 

3743 6. If there is something to coalesce, coalesce one pair, then goto 3 

3744 """ 

3745 Util.log("=== SR %s: gc ===" % srUuid) 

3746 if inBackground: 

3747 if daemonize(): 3747 ↛ exitline 3747 didn't return from function 'gc', because the condition on line 3747 was never false

3748 # we are now running in the background. Catch & log any errors 

3749 # because there is no other way to propagate them back at this 

3750 # point 

3751 

3752 try: 

3753 _gc(None, srUuid, dryRun) 

3754 except AbortException: 

3755 Util.log("Aborted") 

3756 except Exception: 

3757 Util.logException("gc") 

3758 Util.log("* * * * * SR %s: ERROR\n" % srUuid) 

3759 os._exit(0) 

3760 else: 

3761 _gc(session, srUuid, dryRun, immediate=True) 

3762 

3763 

3764def start_gc(session, sr_uuid): 

3765 """ 

3766 This function is used to try to start a backgrounded GC session by forking 

3767 the current process. If using the systemd version, call start_gc_service() instead. 

3768 """ 

3769 # don't bother if an instance already running (this is just an 

3770 # optimization to reduce the overhead of forking a new process if we 

3771 # don't have to, but the process will check the lock anyways) 

3772 lockRunning = lock.Lock(lock.LOCK_TYPE_GC_RUNNING, sr_uuid) 

3773 if not lockRunning.acquireNoblock(): 

3774 if should_preempt(session, sr_uuid): 

3775 util.SMlog("Aborting currently-running coalesce of garbage VDI") 

3776 try: 

3777 if not abort(sr_uuid, soft=True): 

3778 util.SMlog("The GC has already been scheduled to re-start") 

3779 except util.CommandException as e: 

3780 if e.code != errno.ETIMEDOUT: 

3781 raise 

3782 util.SMlog('failed to abort the GC') 

3783 else: 

3784 util.SMlog("A GC instance already running, not kicking") 

3785 return 

3786 else: 

3787 lockRunning.release() 

3788 

3789 util.SMlog(f"Starting GC file is {__file__}") 

3790 subprocess.run([__file__, '-b', '-u', sr_uuid, '-g'], 

3791 stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3792 

3793def start_gc_service(sr_uuid, wait=False): 

3794 """ 

3795 This starts the templated systemd service which runs GC on the given SR UUID. 

3796 If the service was already started, this is a no-op. 

3797 

3798 Because the service is a one-shot with RemainAfterExit=no, when called with 

3799 wait=True this will run the service synchronously and will not return until the 

3800 run has finished. This is used to force a run of the GC instead of just kicking it 

3801 in the background. 

3802 """ 

3803 sr_uuid_esc = sr_uuid.replace("-", "\\x2d") 

3804 util.SMlog(f"Kicking SMGC@{sr_uuid}...") 

3805 cmd=[ "/usr/bin/systemctl", "--quiet" ] 

3806 if not wait: 3806 ↛ 3808line 3806 didn't jump to line 3808, because the condition on line 3806 was never false

3807 cmd.append("--no-block") 

3808 cmd += ["start", f"SMGC@{sr_uuid_esc}"] 

3809 subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3810 

3811 

3812def gc_force(session, srUuid, force=False, dryRun=False, lockSR=False): 

3813 """Garbage collect all deleted VDIs in SR "srUuid". The caller must ensure 

3814 the SR lock is held. 

3815 The following algorithm is used: 

3816 1. If we are already GC'ing or coalescing a VDI pair, abort GC/coalesce 

3817 2. Scan the SR 

3818 3. GC 

3819 4. return 

3820 """ 

3821 Util.log("=== SR %s: gc_force ===" % srUuid) 

3822 init(srUuid) 

3823 sr = SR.getInstance(srUuid, session, lockSR, True) 

3824 if not lockGCActive.acquireNoblock(): 

3825 abort(srUuid) 

3826 else: 

3827 Util.log("Nothing was running, clear to proceed") 

3828 

3829 if force: 

3830 Util.log("FORCED: will continue even if there are VHD errors") 

3831 sr.scanLocked(force) 

3832 sr.cleanupCoalesceJournals() 

3833 

3834 try: 

3835 sr.cleanupCache() 

3836 sr.garbageCollect(dryRun) 

3837 finally: 

3838 sr.cleanup() 

3839 sr.logFilter.logState() 

3840 lockGCActive.release() 

3841 

3842 

3843def get_state(srUuid): 

3844 """Return whether GC/coalesce is currently running or not. This asks systemd for 

3845 the state of the templated SMGC service and will return True if it is "activating" 

3846 or "running" (for completeness, as in practice it will never achieve the latter state) 

3847 """ 

3848 sr_uuid_esc = srUuid.replace("-", "\\x2d") 

3849 cmd=[ "/usr/bin/systemctl", "is-active", f"SMGC@{sr_uuid_esc}"] 

3850 result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) 

3851 state = result.stdout.decode('utf-8').rstrip() 

3852 if state == "activating" or state == "running": 

3853 return True 

3854 return False 

3855 

3856 

3857def should_preempt(session, srUuid): 

3858 sr = SR.getInstance(srUuid, session) 

3859 entries = sr.journaler.getAll(VDI.JRN_COALESCE) 

3860 if len(entries) == 0: 

3861 return False 

3862 elif len(entries) > 1: 

3863 raise util.SMException("More than one coalesce entry: " + str(entries)) 

3864 sr.scanLocked() 

3865 coalescedUuid = entries.popitem()[0] 

3866 garbage = sr.findGarbage() 

3867 for vdi in garbage: 

3868 if vdi.uuid == coalescedUuid: 

3869 return True 

3870 return False 

3871 

3872 

3873def get_coalesceable_leaves(session, srUuid, vdiUuids): 

3874 coalesceable = [] 

3875 sr = SR.getInstance(srUuid, session) 

3876 sr.scanLocked() 

3877 for uuid in vdiUuids: 

3878 vdi = sr.getVDI(uuid) 

3879 if not vdi: 

3880 raise util.SMException("VDI %s not found" % uuid) 

3881 if vdi.isLeafCoalesceable(): 

3882 coalesceable.append(uuid) 

3883 return coalesceable 

3884 

3885 

3886def cache_cleanup(session, srUuid, maxAge): 

3887 sr = SR.getInstance(srUuid, session) 

3888 return sr.cleanupCache(maxAge) 

3889 

3890 

3891def debug(sr_uuid, cmd, vdi_uuid): 

3892 Util.log("Debug command: %s" % cmd) 

3893 sr = SR.getInstance(sr_uuid, None) 

3894 if not isinstance(sr, LVHDSR): 

3895 print("Error: not an LVHD SR") 

3896 return 

3897 sr.scanLocked() 

3898 vdi = sr.getVDI(vdi_uuid) 

3899 if not vdi: 

3900 print("Error: VDI %s not found") 

3901 return 

3902 print("Running %s on SR %s" % (cmd, sr)) 

3903 print("VDI before: %s" % vdi) 

3904 if cmd == "activate": 

3905 vdi._activate() 

3906 print("VDI file: %s" % vdi.path) 

3907 if cmd == "deactivate": 

3908 ns = lvhdutil.NS_PREFIX_LVM + sr.uuid 

3909 sr.lvmCache.deactivate(ns, vdi.uuid, vdi.fileName, False) 

3910 if cmd == "inflate": 

3911 vdi.inflateFully() 

3912 sr.cleanup() 

3913 if cmd == "deflate": 

3914 vdi.deflate() 

3915 sr.cleanup() 

3916 sr.scanLocked() 

3917 print("VDI after: %s" % vdi) 

3918 

3919 

3920def abort_optional_reenable(uuid): 

3921 print("Disabling GC/coalesce for %s" % uuid) 

3922 ret = _abort(uuid) 

3923 input("Press enter to re-enable...") 

3924 print("GC/coalesce re-enabled") 

3925 lockGCRunning.release() 

3926 if ret: 

3927 lockGCActive.release() 

3928 

3929 

3930############################################################################## 

3931# 

3932# CLI 

3933# 

3934def main(): 

3935 action = "" 

3936 uuid = "" 

3937 background = False 

3938 force = False 

3939 dryRun = False 

3940 debug_cmd = "" 

3941 vdi_uuid = "" 

3942 shortArgs = "gGc:aqxu:bfdt:v:" 

3943 longArgs = ["gc", "gc_force", "clean_cache", "abort", "query", "disable", 

3944 "uuid=", "background", "force", "dry-run", "debug=", "vdi_uuid="] 

3945 

3946 try: 

3947 opts, args = getopt.getopt(sys.argv[1:], shortArgs, longArgs) 

3948 except getopt.GetoptError: 

3949 usage() 

3950 for o, a in opts: 

3951 if o in ("-g", "--gc"): 

3952 action = "gc" 

3953 if o in ("-G", "--gc_force"): 

3954 action = "gc_force" 

3955 if o in ("-c", "--clean_cache"): 

3956 action = "clean_cache" 

3957 maxAge = int(a) 

3958 if o in ("-a", "--abort"): 

3959 action = "abort" 

3960 if o in ("-q", "--query"): 

3961 action = "query" 

3962 if o in ("-x", "--disable"): 

3963 action = "disable" 

3964 if o in ("-u", "--uuid"): 

3965 uuid = a 

3966 if o in ("-b", "--background"): 

3967 background = True 

3968 if o in ("-f", "--force"): 

3969 force = True 

3970 if o in ("-d", "--dry-run"): 

3971 Util.log("Dry run mode") 

3972 dryRun = True 

3973 if o in ("-t", "--debug"): 

3974 action = "debug" 

3975 debug_cmd = a 

3976 if o in ("-v", "--vdi_uuid"): 

3977 vdi_uuid = a 

3978 

3979 if not action or not uuid: 

3980 usage() 

3981 if action == "debug" and not (debug_cmd and vdi_uuid) or \ 

3982 action != "debug" and (debug_cmd or vdi_uuid): 

3983 usage() 

3984 

3985 if action != "query" and action != "debug": 

3986 print("All output goes to log") 

3987 

3988 if action == "gc": 

3989 gc(None, uuid, background, dryRun) 

3990 elif action == "gc_force": 

3991 gc_force(None, uuid, force, dryRun, True) 

3992 elif action == "clean_cache": 

3993 cache_cleanup(None, uuid, maxAge) 

3994 elif action == "abort": 

3995 abort(uuid) 

3996 elif action == "query": 

3997 print("Currently running: %s" % get_state(uuid)) 

3998 elif action == "disable": 

3999 abort_optional_reenable(uuid) 

4000 elif action == "debug": 

4001 debug(uuid, debug_cmd, vdi_uuid) 

4002 

4003 

4004if __name__ == '__main__': 4004 ↛ 4005line 4004 didn't jump to line 4005, because the condition on line 4004 was never true

4005 main()