Coverage for drivers/cleanup.py : 31%

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#
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
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
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
61 LINSTOR_AVAILABLE = True
62except ImportError:
63 LINSTOR_AVAILABLE = False
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
72FLAG_TYPE_ABORT = "abort" # flag to request aborting of GC/coalesce
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
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
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
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"
93N_RUNNING_AVERAGE = 10
95NON_PERSISTENT_DIR = '/run/nonpersistent/sm'
98class AbortException(util.SMException):
99 pass
102################################################################################
103#
104# Util
105#
106class Util:
107 RET_RC = 1
108 RET_STDOUT = 2
109 RET_STDERR = 4
111 UUID_LEN = 36
113 PREFIX = {"G": 1024 * 1024 * 1024, "M": 1024 * 1024, "K": 1024}
115 def log(text):
116 util.SMlog(text, ident="SMGC")
117 log = staticmethod(log)
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)
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)
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)
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
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)
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)
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)
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
243 count = 0
244 for i in range(lenShort):
245 val = bitmap1[i] | bitmap2[i]
246 count += Util.numBits(val)
248 for i in range(i + 1, lenLong):
249 val = bitmapLong[i]
250 count += Util.numBits(val)
251 return count
252 countBits = staticmethod(countBits)
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)
262################################################################################
263#
264# XAPI
265#
266class XAPI:
267 USER = "root"
268 PLUGIN_ON_SLAVE = "on-slave"
270 CONFIG_SM = 0
271 CONFIG_OTHER = 1
272 CONFIG_ON_BOOT = 2
273 CONFIG_ALLOW_CACHING = 3
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 }
282 class LookupError(util.SMException):
283 pass
285 def getSession():
286 session = XenAPI.xapi_local()
287 session.xenapi.login_with_password(XAPI.USER, '', '', 'SM')
288 return session
289 getSession = staticmethod(getSession)
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}
304 def __del__(self):
305 if self.sessionPrivate:
306 self.session.xenapi.session.logout()
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
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
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
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
345 def getOnlineHosts(self):
346 return util.get_online_hosts(self.session)
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)
353 def getRecordHost(self, hostRef):
354 return self.session.xenapi.host.get_record(hostRef)
356 def _getRefVDI(self, uuid):
357 return self.session.xenapi.VDI.get_by_uuid(uuid)
359 def getRefVDI(self, vdi):
360 return self._getRefVDI(vdi.uuid)
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
369 def singleSnapshotVDI(self, vdi):
370 return self.session.xenapi.VDI.snapshot(vdi.getRef(),
371 {"type": "internal"})
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
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
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)
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)
415 def isSnapshot(self, vdi):
416 return self.session.xenapi.VDI.get_is_a_snapshot(vdi.getRef())
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)
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.")
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)
464 def create_task(self, label, description):
465 self.task = self.session.xenapi.task.create(label, description)
466 self.update_task()
468 def update_task_progress(self, key, value):
469 self.task_progress[key] = value
470 if self.task:
471 self.update_task()
473 def set_task_status(self, status):
474 if self.task:
475 self.session.xenapi.task.set_status(self.task, status)
478################################################################################
479#
480# VDI
481#
482class VDI(object):
483 """Object representing a VDI of a VHD-based SR"""
485 POLL_INTERVAL = 1
486 POLL_TIMEOUT = 30
487 DEVICE_MAJOR = 202
488 DRIVER_NAME_VHD = "vhd"
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"
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 }
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
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
533 STR_TREE_INDENT = 4
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()
551 @staticmethod
552 def extractUuid(path):
553 raise NotImplementedError("Implement in sub class")
555 def load(self, info=None):
556 """Load VDI info"""
557 pass # abstract
559 def getDriverName(self):
560 return self.DRIVER_NAME_VHD
562 def getRef(self):
563 if self._vdiRef is None:
564 self._vdiRef = self.sr.xapi.getRefVDI(self)
565 return self._vdiRef
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
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))
582 def delConfig(self, key):
583 self.sr.xapi.removeFromConfigVDI(self, key)
584 Util.log("Removed %s from %s" % (key, self))
586 def ensureUnpaused(self):
587 if self.getConfig(self.DB_VDI_PAUSED) == "true":
588 Util.log("Unpausing VDI %s" % self)
589 self.unpause()
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)
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)
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)
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()
631 def isSnapshot(self):
632 return self.sr.xapi.isSnapshot(self)
634 def isAttachedRW(self):
635 return util.is_attached_rw(
636 self.sr.xapi.session.xenapi.VDI.get_sm_config(self.getRef()))
638 def getVHDBlocks(self):
639 val = self.updateBlockInfo()
640 bitmap = zlib.decompress(base64.b64decode(val))
641 return bitmap
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
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
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
673 return (feasibleSize or
674 self.getConfig(self.DB_LEAFCLSC) == self.LEAFCLSC_FORCE)
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 []
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
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`.
708 if not self.scanError and not self.hidden and thisPrunable:
709 vdiList.append(self)
710 return vdiList
712 def getSizeVHD(self):
713 return self._sizeVHD
715 def getAllocatedSize(self):
716 return self._sizeAllocated
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
725 def getTreeHeight(self):
726 "Get the height of the subtree rooted at self"
727 if len(self.children) == 0:
728 return 1
730 maxChildHeight = 0
731 for child in self.children:
732 childHeight = child.getTreeHeight()
733 if childHeight > maxChildHeight:
734 maxChildHeight = childHeight
736 return maxChildHeight + 1
738 def getAllLeaves(self):
739 "Get all leaf nodes in the subtree rooted at self"
740 if len(self.children) == 0:
741 return [self]
743 leaves = []
744 for child in self.children:
745 leaves.extend(child.getAllLeaves())
746 return leaves
748 def updateBlockInfo(self):
749 val = base64.b64encode(self._queryVHDBlocks()).decode()
750 self.setConfig(VDI.DB_VHD_BLOCKS, val)
751 return val
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
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()
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
773 def repair(self, parent):
774 vhdutil.repair(parent)
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 = ""
794 return "%s%s(%s%s%s)%s" % (strHidden, self.uuid[0:8], strSizeVirt,
795 strSizeVHD, strSizeAllocated, strType)
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)
801 def _clear(self):
802 self.uuid = ""
803 self.path = ""
804 self.parentUuid = ""
805 self.parent = None
806 self._clearRef()
808 def _clearRef(self):
809 self._vdiRef = None
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()
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")
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
839 def _reportCoalesceError(vdi, ce):
840 """Reports a coalesce error to XenCenter.
842 vdi: the VDI object on which the coalesce error occured
843 ce: the CommandException that was raised"""
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))
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
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)
893 def coalesce(self):
894 # size is returned in sectors
895 return vhdutil.coalesce(self.path) * 512
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)
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
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
952 util.fistpoint.activate("LVHDRT_coalescing_VHD_data", self.sr.uuid)
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 = []
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()
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()
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()
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")
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)
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
1017 for child in self.children:
1018 child._tagChildrenForRelink()
1020 def _loadInfoParent(self):
1021 ret = vhdutil.getParent(self.path, lvhdutil.extractUuid)
1022 if ret:
1023 self.parentUuid = ret
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))
1038 def _loadInfoHidden(self):
1039 hidden = vhdutil.getHidden(self.path)
1040 self.hidden = (hidden != 0)
1042 def _setHidden(self, hidden=True):
1043 vhdutil.setHidden(self.path, hidden)
1044 self.hidden = hidden
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)))
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)
1076 self.sizeVirt = vhdutil.getSizeVirt(self.path)
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)
1085 def _queryVHDBlocks(self):
1086 return vhdutil.getBlockBitmap(self.path)
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
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()
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()
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
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
1131class FileVDI(VDI):
1132 """Object representing a VDI in a file-based SR (EXT or NFS)"""
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
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)
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))
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)
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)
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
1202class LVHDVDI(VDI):
1203 """Object representing a VDI in an LVHD SR"""
1205 JRN_ZERO = "zero" # journal entry type for zeroing out end of parent
1206 DRIVER_NAME_RAW = "aio"
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)
1224 @staticmethod
1225 def extractUuid(path):
1226 return lvhdutil.extractUuid(path)
1228 def getDriverName(self):
1229 if self.raw:
1230 return self.DRIVER_NAME_RAW
1231 return self.DRIVER_NAME_VHD
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
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
1262 def inflateFully(self):
1263 self.inflate(lvhdutil.calcSizeVHDLV(self.sizeVirt))
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)
1275 def updateBlockInfo(self):
1276 if not self.raw:
1277 return VDI.updateBlockInfo(self)
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))
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)
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)
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)
1311 def getSizeVHD(self):
1312 if self._sizeVHD == -1:
1313 self._loadInfoSizeVHD()
1314 return self._sizeVHD
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))
1329 def getAllocatedSize(self):
1330 if self._sizeAllocated == -1:
1331 self._loadInfoSizeAllocated()
1332 return self._sizeAllocated
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)
1343 def _loadInfoHidden(self):
1344 if self.raw:
1345 self.hidden = self.sr.lvmCache.getHidden(self.fileName)
1346 else:
1347 VDI._loadInfoHidden(self)
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)
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)
1378 def validate(self, fast=False):
1379 if not self.raw:
1380 VDI.validate(self, fast)
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)
1395 def _setParent(self, parent):
1396 self._activate()
1397 if self.lvReadonly:
1398 self.sr.lvmCache.setReadonly(self.fileName, False)
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))
1417 def _activate(self):
1418 self.sr.lvActivator.activate(self.uuid, self.fileName, False)
1420 def _activateChain(self):
1421 vdi = self
1422 while vdi:
1423 vdi._activate()
1424 vdi = vdi.parent
1426 def _deactivate(self):
1427 self.sr.lvActivator.deactivate(self.uuid, False)
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
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
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)
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)
1478 def _queryVHDBlocks(self):
1479 self._activate()
1480 return VDI._queryVHDBlocks(self)
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
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
1496 def _calcExtraSpaceForSnapshotCoalescing(self):
1497 return self._calcExtraSpaceForCoalescing() + \
1498 lvhdutil.calcSizeLV(self.getSizeVHD())
1501class LinstorVDI(VDI):
1502 """Object representing a VDI in a LINSTOR SR"""
1504 VOLUME_LOCK_TIMEOUT = 30
1506 def load(self, info=None):
1507 self.parentUuid = info.parentUuid
1508 self.scanError = True
1509 self.parent = None
1510 self.children = []
1512 self.fileName = self.sr._linstor.get_volume_name(self.uuid)
1513 self.path = self.sr._linstor.build_device_path(self.fileName)
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
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
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
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
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
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
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
1579 def inflateFully(self):
1580 if not self.raw:
1581 self.inflate(LinstorVhdUtil.compute_volume_size(self.sizeVirt, self.vdi_type))
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)
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)
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))
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)
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
1618 def getParent(self):
1619 return self.sr._vhdutil.get_parent(
1620 self.sr._linstor.get_volume_uuid_from_device_path(self.path)
1621 )
1623 def repair(self, parent_uuid):
1624 self.sr._vhdutil.force_repair(
1625 self.sr._linstor.get_device_path(parent_uuid)
1626 )
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 )
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 = []
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))
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()
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
1686 def _setHidden(self, hidden=True):
1687 HIDDEN_TAG = 'hidden'
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)
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
1712 def _queryVHDBlocks(self):
1713 return self.sr._vhdutil.get_block_bitmap(self.uuid)
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)
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()
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
1738 def _calcExtraSpaceForSnapshotCoalescing(self):
1739 assert self.getSizeVHD() > 0
1740 return self._calcExtraSpaceForCoalescing() + \
1741 LinstorVolumeManager.round_up_volume_size(self.getSizeVHD())
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 = {}
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]
1764 for uuid in self.prevState:
1765 if not self.currState.get(uuid):
1766 changes += "Tree %s gone\n" % uuid
1768 result = "SR %s (%d VDIs in %d VHD trees): " % \
1769 (self.sr, len(self.sr.vdis), len(self.sr.vdiTrees))
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"
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
1785 def logNewVDI(self, uuid):
1786 if self.stateLogged:
1787 Util.log("Found new VDI when scanning: %s" % uuid)
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
1795 TYPE_FILE = "file"
1796 TYPE_LVHD = "lvhd"
1797 TYPE_LINSTOR = "linstor"
1798 TYPES = [TYPE_LVHD, TYPE_FILE, TYPE_LINSTOR]
1800 LOCK_RETRY_INTERVAL = 3
1801 LOCK_RETRY_ATTEMPTS = 20
1802 LOCK_RETRY_ATTEMPTS_LOCK = 100
1804 SCAN_RETRY_ATTEMPTS = 3
1806 JRN_CLONE = "clone" # journal entry type for the clone operation (from SM)
1807 TMP_RENAME_PREFIX = "OLD_"
1809 KEY_OFFLINE_COALESCE_NEEDED = "leaf_coalesce_need_offline"
1810 KEY_OFFLINE_COALESCE_OVERRIDE = "leaf_coalesce_offline_override"
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)
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 = []
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)
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")
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
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
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
1875 def scanLocked(self, force=False):
1876 self.lock()
1877 try:
1878 self.scan(force)
1879 finally:
1880 self.unlock()
1882 def getVDI(self, uuid):
1883 return self.vdis.get(uuid)
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
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"""
1901 candidates = []
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
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
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)
1920 self.xapi.update_task_progress("coalescable", len(candidates))
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]
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
1945 def getSwitch(self, key):
1946 return self.xapi.srRecord["other_config"].get(key)
1948 def forbiddenBySwitch(self, switch, condition, fail_msg):
1949 srSwitch = self.getSwitch(switch)
1950 ret = False
1951 if srSwitch:
1952 ret = srSwitch == condition
1954 if ret:
1955 Util.log(fail_msg)
1957 return ret
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"))
1967 def findLeafCoalesceable(self):
1968 """Find leaf-coalesceable VDIs in each VHD tree"""
1970 candidates = []
1971 if self.leafCoalesceForbidden():
1972 return candidates
1974 self.gatherLeafCoalesceable(candidates)
1976 self.xapi.update_task_progress("coalescable", len(candidates))
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
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)
2000 return None
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)
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
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()
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
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()
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)
2073 def findGarbage(self):
2074 vdiList = []
2075 for vdi in self.vdiTrees:
2076 vdiList.extend(vdi.getAllPrunable())
2077 return vdiList
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)
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()
2095 def forgetVDI(self, vdiUuid):
2096 self.xapi.forgetVDI(self.uuid, vdiUuid)
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
2110 if failed:
2111 self.unpauseVDIs(paused)
2112 raise util.SMException("Failed to pause VDIs")
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")
2125 def getFreeSpace(self):
2126 return 0
2128 def cleanup(self):
2129 Util.log("In cleanup")
2130 return
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
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
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")
2157 self._locked += 1
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()
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
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()
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)
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)
2204 def cleanupCache(self, maxAge=-1):
2205 return 0
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)
2221 util.fistpoint.activate("LVHDRT_before_create_relink_journal", self.uuid)
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")
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)
2239 self.journaler.remove(vdi.JRN_RELINK, vdi.uuid)
2240 self.deleteVDI(vdi)
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}"
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
2260 def abortCoalesce(self, prevSize, curSize):
2261 res = False
2263 self.its += 1
2264 self.history.append(self.HISTORY_STRING.format(its=self.its,
2265 initSize=prevSize,
2266 finSize=curSize))
2268 self.finishSize = curSize
2270 if self.startSize is None:
2271 self.startSize = prevSize
2273 if curSize < self.minSize:
2274 self.minSize = curSize
2276 if prevSize < self.minSize:
2277 self.minSize = prevSize
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)
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
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
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
2305 return res
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))
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)
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
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)
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()
2366 def recordStorageSpeed(self, startTime, endTime, vhdSize):
2367 speed = self.calcStorageSpeed(startTime, endTime, vhdSize)
2368 if speed is None:
2369 return
2371 self.writeSpeedToFile(speed)
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
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()
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
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
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
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)
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)
2503 # Note that "vdi.parent" is now the single remaining leaf and "vdi" is
2504 # garbage
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)
2513 self._updateNode(vdi)
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
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)
2531 util.fistpoint.activate("LVHDRT_coaleaf_before_remove_j", self.uuid)
2532 self.journaler.remove(VDI.JRN_LEAF, vdiUuid)
2534 self.forgetVDI(origParentUuid)
2535 self._finishCoalesceLeaf(parent)
2536 self._updateSlavesOnResize(parent)
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
2545 def _prepareCoalesceLeaf(self, vdi):
2546 pass
2548 def _updateNode(self, vdi):
2549 pass
2551 def _finishCoalesceLeaf(self, parent):
2552 pass
2554 def _updateSlavesOnUndoLeafCoalesce(self, parent, child):
2555 pass
2557 def _updateSlavesOnRename(self, vdi, oldName, origParentUuid):
2558 pass
2560 def _updateSlavesOnResize(self, vdi):
2561 pass
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]
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
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)
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
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)
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()
2640 def getFreeSpace(self):
2641 return util.get_fs_size(self.path) - util.get_fs_utilisation(self.path)
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()
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
2682 if action == self.CACHE_ACTION_KEEP:
2683 Util.log("Keeping cache %s" % uuid)
2684 continue
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
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
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
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()
2718 Util.log("Deleting file %s" % fullPath)
2719 os.unlink(fullPath)
2720 return True
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)
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")
2742 def deleteVDI(self, vdi):
2743 self._checkSlaves(vdi)
2744 SR.deleteVDI(self, vdi)
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
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)
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()
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)
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)
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)
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")
2834class LVHDSR(SR):
2835 TYPE = SR.TYPE_LVHD
2836 SUBTYPES = ["lvhdoiscsi", "lvhdohba"]
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)
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)
2848 self.lvActivator = LVActivator(self.uuid, self.lvmCache)
2849 self.journaler = journaler.Journaler(self.lvmCache)
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)
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)
2862 def getFreeSpace(self):
2863 stats = lvutil._getVGstats(self.vgName)
2864 return stats['physical_size'] - stats['physical_utilisation']
2866 def cleanup(self):
2867 if not self.lvActivator.deactivateAll():
2868 Util.log("ERROR deactivating LVs while cleaning up")
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
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()
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()
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")
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)
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)
2942 return SR._liveLeafCoalesce(self, vdi)
2944 def _prepareCoalesceLeaf(self, vdi):
2945 vdi._activateChain()
2946 self.lvmCache.setReadonly(vdi.parent.fileName, False)
2947 vdi.deflate()
2948 vdi.inflateParentForCoalesce()
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)
2964 def _finishCoalesceLeaf(self, parent):
2965 if not parent.isSnapshot() or parent.isAttachedRW():
2966 parent.inflateFully()
2967 else:
2968 parent.deflate()
2970 def _calcExtraSpaceNeeded(self, child, parent):
2971 return lvhdutil.calcSizeVHDLV(parent.sizeVirt) - parent.sizeLV
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()
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)
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)
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)
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)
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")
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
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
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)
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
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)
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)
3142class LinstorSR(SR):
3143 TYPE = SR.TYPE_LINSTOR
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 )
3151 SR.__init__(self, uuid, xapi, createLock, force)
3152 self.path = LinstorVolumeManager.DEV_ROOT_PATH
3153 self._reloadLinstor()
3155 def deleteVDI(self, vdi):
3156 self._checkSlaves(vdi)
3157 SR.deleteVDI(self, vdi)
3159 def getFreeSpace(self):
3160 return self._linstor.max_volume_size_allowed
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()
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)
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)
3189 pbd = util.find_my_pbd(session, host_ref, sr_ref)
3190 if pbd is None:
3191 raise util.SMException('Failed to find PBD')
3193 dconf = session.xenapi.PBD.get_device_config(pbd)
3194 group_name = dconf['group-name']
3196 controller_uri = get_controller_uri()
3197 self.journaler = LinstorJournaler(
3198 controller_uri, group_name, logger=util.SMlog
3199 )
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)
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())
3226 if force:
3227 return all_vdi_info
3228 raise util.SMException('Scan error')
3230 def _load_vdi_info(self):
3231 all_vdi_info = {}
3233 # TODO: Ensure metadata contains the right info.
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.
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))
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
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
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
3291 all_vdi_info[vdi_uuid] = info
3293 return all_vdi_info
3295 def _prepareCoalesceLeaf(self, vdi):
3296 vdi._activateChain()
3297 vdi.deflate()
3298 vdi._inflateParentForCoalesce()
3300 def _finishCoalesceLeaf(self, parent):
3301 if not parent.isSnapshot() or parent.isAttachedRW():
3302 parent.inflateFully()
3303 else:
3304 parent.deflate()
3306 def _calcExtraSpaceNeeded(self, child, parent):
3307 return LinstorVhdUtil.compute_volume_size(parent.sizeVirt, parent.vdi_type) - parent.getDrbdSize()
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
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()
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()
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)
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)
3369 # TODO: Maybe deflate here.
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')
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')
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
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
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
3452GCPAUSE_DEFAULT_SLEEP = 5 * 60
3455def _gc_init_file(sr_uuid):
3456 return os.path.join(NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init')
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')
3466def _gcLoopPause(sr, dryRun=False, immediate=False):
3467 if immediate:
3468 return
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.
3473 if util.fistpoint.is_active(util.GCPAUSE_FISTPOINT):
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)
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")
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
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
3501 # Clean up Intellicache files
3502 sr.cleanupCache()
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
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
3536 sr.xapi.update_task_progress("done", coalesced)
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()
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()
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
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
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()
3582def _xapi_enabled(session, hostref):
3583 host = session.xenapi.host.get_record(hostref)
3584 return host['enabled']
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
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()
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
3612 try:
3613 _gcLoop(sr, dryRun, immediate=immediate)
3614 finally:
3615 sr.cleanup()
3616 sr.logFilter.logState()
3617 del sr.xapi
3620def _abort(srUuid, soft=False):
3621 """Aborts an GC/coalesce.
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.
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
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)
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)
3668 def acquireNoblock(self):
3669 self._srLock.acquire()
3671 try:
3672 return self._lock.acquireNoblock()
3673 finally:
3674 self._srLock.release()
3676 def release(self):
3677 self._lock.release()
3680def usage():
3681 output = """Garbage collect and/or coalesce VHDs in a VHD-based SR
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
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)
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)
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
3730def gc(session, srUuid, inBackground, dryRun=False):
3731 """Garbage collect all deleted VDIs in SR "srUuid". Fork & return
3732 immediately if inBackground=True.
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
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)
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()
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)
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.
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)
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")
3829 if force:
3830 Util.log("FORCED: will continue even if there are VHD errors")
3831 sr.scanLocked(force)
3832 sr.cleanupCoalesceJournals()
3834 try:
3835 sr.cleanupCache()
3836 sr.garbageCollect(dryRun)
3837 finally:
3838 sr.cleanup()
3839 sr.logFilter.logState()
3840 lockGCActive.release()
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
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
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
3886def cache_cleanup(session, srUuid, maxAge):
3887 sr = SR.getInstance(srUuid, session)
3888 return sr.cleanupCache(maxAge)
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)
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()
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="]
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
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()
3985 if action != "query" and action != "debug":
3986 print("All output goes to log")
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)
4004if __name__ == '__main__': 4004 ↛ 4005line 4004 didn't jump to line 4005, because the condition on line 4004 was never true
4005 main()