Skip to content

Commit ab0cf81

Browse files
committed
Coalesce on remote WIP
Signed-off-by: Damien Thenot <[email protected]>
1 parent d934f92 commit ab0cf81

File tree

7 files changed

+157
-28
lines changed

7 files changed

+157
-28
lines changed

drivers/blktap2.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
LINSTOR_AVAILABLE = False
6565

6666
PLUGIN_TAP_PAUSE = "tapdisk-pause"
67+
PLUGIN_ON_SLAVE = "on-slave"
6768

6869
SOCKPATH = "/var/xapi/xcp-rrdd"
6970

@@ -1658,6 +1659,8 @@ def _activate_locked(self, sr_uuid, vdi_uuid, options):
16581659
if self.tap_wanted():
16591660
if not self._add_tag(vdi_uuid, not options["rdonly"]):
16601661
return False
1662+
#TODO: Need to interrupt coalesce on master, the coalesce will check for host_OpaqueRef on the VDI before trying offline coalesce
1663+
#TODO: The coalesce could happen on another slave in onlinecoalesce, interrupt coalesce on another slave (online coalesce)?
16611664
refresh = True
16621665

16631666
try:

drivers/cleanup.py

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -757,7 +757,7 @@ def getTreeHeight(self):
757757

758758
return maxChildHeight + 1
759759

760-
def getAllLeaves(self):
760+
def getAllLeaves(self) -> List["VDI"]:
761761
"Get all leaf nodes in the subtree rooted at self"
762762
if len(self.children) == 0:
763763
return [self]
@@ -828,6 +828,49 @@ def _clear(self):
828828
def _clearRef(self):
829829
self._vdiRef = None
830830

831+
def _call_plugin_coalesce(self, hostRef):
832+
args = {"path": self.path, "vdi_type": self.vdi_type}
833+
self.sr.xapi.session.xenapi.host.call_plugin( \
834+
hostRef, XAPI.PLUGIN_ON_SLAVE, "commit_tapdisk", args)
835+
836+
def _doCoalesceOnHost(self, hostRef):
837+
self.validate()
838+
self.parent.validate(True)
839+
self.parent._increaseSizeVirt(self.sizeVirt)
840+
self.sr._updateSlavesOnResize(self.parent)
841+
#TODO: We might need to make the LV RW on the slave directly for coalesce?
842+
843+
def abortTest():
844+
file = self.sr._gc_running_file(self)
845+
try:
846+
with open(file, "r") as f:
847+
if not f.read():
848+
return True
849+
except OSError as e:
850+
if e.errno == errno.ENOENT:
851+
util.SMlog("File {} does not exist".format(file))
852+
else:
853+
util.SMlog("IOError: {}".format(e))
854+
return True
855+
return False
856+
857+
Util.runAbortable(lambda: self._call_plugin_coalesce(hostRef),
858+
None, self.sr.uuid, abortTest, VDI.POLL_INTERVAL, 0)
859+
860+
self.parent.validate(True)
861+
#self._verifyContents(0)
862+
self.parent.updateBlockInfo()
863+
864+
def _isOpenOnHosts(self) -> Optional[str]:
865+
for pbdRecord in self.sr.xapi.getAttachedPBDs():
866+
hostRef = pbdRecord["host"]
867+
args = {"path": self.path}
868+
is_openers = util.strtobool(self.sr.xapi.session.xenapi.host.call_plugin( \
869+
hostRef, XAPI.PLUGIN_ON_SLAVE, "is_openers", args))
870+
if is_openers:
871+
return hostRef
872+
return None
873+
831874
def _doCoalesce(self) -> None:
832875
"""Coalesce self onto parent. Only perform the actual coalescing of
833876
an image, but not the subsequent relinking. We'll do that as the next step,
@@ -914,7 +957,7 @@ def coalesce(self) -> int:
914957
return self.cowutil.coalesce(self.path)
915958

916959
@staticmethod
917-
def _doCoalesceCowImage(vdi):
960+
def _doCoalesceCowImage(vdi: "VDI"):
918961
try:
919962
startTime = time.time()
920963
allocated_size = vdi.getAllocatedSize()
@@ -2383,7 +2426,17 @@ def cleanupJournals(self, dryRun=False):
23832426
def cleanupCache(self, maxAge=-1) -> int:
23842427
return 0
23852428

2386-
def _coalesce(self, vdi):
2429+
def _hasLeavesAttachedOn(self, vdi: VDI):
2430+
leaves = vdi.getAllLeaves()
2431+
leaves_vdi = [leaf.uuid for leaf in leaves]
2432+
return util.get_hosts_attached_on(self.xapi.session, leaves_vdi)
2433+
2434+
def _gc_running_file(self, vdi):
2435+
run_file = "gc_running_{}".format(vdi.uuid)
2436+
return os.path.join(NON_PERSISTENT_DIR, str(self.uuid), run_file)
2437+
2438+
def _coalesce(self, vdi: VDI):
2439+
skipRelink = False
23872440
if self.journaler.get(vdi.JRN_RELINK, vdi.uuid):
23882441
# this means we had done the actual coalescing already and just
23892442
# need to finish relinking and/or refreshing the children
@@ -2393,8 +2446,35 @@ def _coalesce(self, vdi):
23932446
# order to decide whether to abort the coalesce. We remove the
23942447
# journal as soon as the COW coalesce step is done, because we
23952448
# don't expect the rest of the process to take long
2449+
2450+
#TODO: Create `gc_running` in `/run/nonpersistent/sm/<sr uuid>/`
2451+
if os.path.exists(self._gc_running_file(vdi)):
2452+
util.SMlog("gc_running already exist for {}. Ignoring...".format(self.uuid))
2453+
2454+
with open(self._gc_running_file(vdi), "w") as f:
2455+
f.write("1")
2456+
23962457
self.journaler.create(vdi.JRN_COALESCE, vdi.uuid, "1")
2397-
vdi._doCoalesce()
2458+
host_refs = self._hasLeavesAttachedOn(vdi)
2459+
#TODO: this check of multiple host_refs should be done earlier in `is_coalesceable` to avoid stopping this late every time
2460+
if len(host_refs) > 1:
2461+
util.SMlog("Not coalesceable, chain activated more than once")
2462+
raise Exception("Not coalesceable, chain activated more than once") #TODO: Use correct error
2463+
2464+
try:
2465+
if host_refs and vdi.cowutil.coalesceOnRemote:
2466+
#Leaf opened on another host, we need to call online coalesce
2467+
vdi._doCoalesceOnHost(host_refs[0])
2468+
skipRelink = True
2469+
else:
2470+
vdi._doCoalesce()
2471+
except:
2472+
os.unlink(self._gc_running_file(vdi))
2473+
raise
2474+
"""
2475+
vdi._doCoalesce will call vdi._coalesceCowImage (after doing other things).
2476+
It will then call VDI._doCoalesceCowImage in a runAbortable context
2477+
"""
23982478
self.journaler.remove(vdi.JRN_COALESCE, vdi.uuid)
23992479

24002480
util.fistpoint.activate("LVHDRT_before_create_relink_journal", self.uuid)
@@ -2403,19 +2483,22 @@ def _coalesce(self, vdi):
24032483
# like SM.clone from manipulating the VDIs we'll be relinking and
24042484
# rescan the SR first in case the children changed since the last
24052485
# scan
2406-
self.journaler.create(vdi.JRN_RELINK, vdi.uuid, "1")
2486+
if not skipRelink:
2487+
self.journaler.create(vdi.JRN_RELINK, vdi.uuid, "1")
24072488

2408-
self.lock()
2409-
try:
2410-
vdi.parent._tagChildrenForRelink()
2411-
self.scan()
2412-
vdi._relinkSkip()
2413-
finally:
2414-
self.unlock()
2415-
# Reload the children to leave things consistent
2416-
vdi.parent._reloadChildren(vdi)
2489+
if not skipRelink:
2490+
self.lock()
2491+
try:
2492+
vdi.parent._tagChildrenForRelink()
2493+
self.scan()
2494+
vdi._relinkSkip()
2495+
finally:
2496+
self.unlock()
2497+
# Reload the children to leave things consistent
2498+
vdi.parent._reloadChildren(vdi)
2499+
self.journaler.remove(vdi.JRN_RELINK, vdi.uuid)
24172500

2418-
self.journaler.remove(vdi.JRN_RELINK, vdi.uuid)
2501+
os.unlink(self._gc_running_file(vdi))
24192502
self.deleteVDI(vdi)
24202503

24212504
class CoalesceTracker:
@@ -3718,8 +3801,7 @@ def _gc_init_file(sr_uuid):
37183801

37193802
def _create_init_file(sr_uuid):
37203803
util.makedirs(os.path.join(NON_PERSISTENT_DIR, str(sr_uuid)))
3721-
with open(os.path.join(
3722-
NON_PERSISTENT_DIR, str(sr_uuid), 'gc_init'), 'w+') as f:
3804+
with open(os.path.join(_gc_init_file(sr_uuid)), 'w+') as f:
37233805
f.write('1')
37243806

37253807

@@ -3748,7 +3830,7 @@ def abortTest():
37483830
Util.log("GC active, quiet period ended")
37493831

37503832

3751-
def _gcLoop(sr, dryRun=False, immediate=False):
3833+
def _gcLoop(sr: SR, dryRun=False, immediate=False):
37523834
if not lockGCActive.acquireNoblock():
37533835
Util.log("Another GC instance already active, exiting")
37543836
return

drivers/cowutil.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,14 @@ def getKeyHash(self, path: str) -> Optional[str]:
266266
def setKey(self, path: str, key_hash: str) -> None:
267267
pass
268268

269+
@abstractmethod
270+
def coalesceOnRemote(self) -> bool:
271+
pass
272+
273+
@abstractmethod
274+
def coalesceOnline(self, path: str) -> int:
275+
pass
276+
269277
def getParentChain(self, lvName: str, extractUuidFunction: Callable[[str], str], vgName: str) -> Dict[str, str]:
270278
"""
271279
Get the chain of all parents of 'path'. Safe to call for raw VDI's as well.

drivers/on_slave.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ def is_open(session, args):
148148
util.logException("is_open")
149149
raise
150150

151-
152151
def refresh_lun_size_by_SCSIid(session, args):
153152
"""Refresh the size of LUNs backing the SCSIid on the local node."""
154153
util.SMlog("on-slave.refresh_lun_size_by_SCSIid(,%s)" % args)
@@ -160,10 +159,35 @@ def refresh_lun_size_by_SCSIid(session, args):
160159
util.SMlog("on-slave.refresh_lun_size_by_SCSIid with %s failed" % args)
161160
return "False"
162161

162+
def commit_tapdisk(session, args):
163+
path = args["path"]
164+
vdi_type = args["vdi_type"]
165+
#TODO: Miss activating/changing readonly, naming should reflect that it does more than coalesceing
166+
from cowutil import getCowUtil
167+
cowutil = getCowUtil(vdi_type)
168+
return str(cowutil.coalesceOnline(path))
169+
170+
def commit_cancel(session, args):
171+
path = args["path"]
172+
vdi_type = args["vdi_type"]
173+
#TODO: HERE CANCEL
174+
# blktap2.cancel()
175+
176+
def is_openers(session, args):
177+
path = args["path"]
178+
openers_pid= util.get_openers_pid(path)
179+
if openers_pid:
180+
return str(True)
181+
else:
182+
return str(False)
163183

164184
if __name__ == "__main__":
165185
import XenAPIPlugin
166186
XenAPIPlugin.dispatch({
167187
"multi": multi,
168188
"is_open": is_open,
169-
"refresh_lun_size_by_SCSIid": refresh_lun_size_by_SCSIid})
189+
"refresh_lun_size_by_SCSIid": refresh_lun_size_by_SCSIid,
190+
"is_openers": is_openers,
191+
"commit_tapdisk": commit_tapdisk,
192+
"commit_cancel": commit_cancel,
193+
})

drivers/qcow2util.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -718,17 +718,17 @@ def getBlockBitmap(self, path: str) -> bytes:
718718
return zlib.compress(self._create_bitmap())
719719

720720
@override
721-
def coalesce(self, path: str) -> int:
722-
pid_openers = util.get_openers_pid(path)
721+
def coalesceOnline(self, path: str) -> int:
722+
pid_openers = util.get_openers_pid(path) # TODO: need to check other hosts and call tap-ctl on the host where it runs
723723
if pid_openers:
724724
if len(pid_openers) > 1:
725-
util.SMlog("Multiple openers for {}".format(path)) #TODO: There might be multiple PID?
725+
raise xs_errors.XenError("Multiple openers for {}".format(path)) # TODO: There might be multiple PID? Yes, we can have the chain enabled for multiple leaf (i.e. after a clone)
726726
pid = pid_openers[0]
727727
l = TapCtl.list(pid=pid)
728728
if len(l) > 1: #TODO: There might more than one minor for this blktap?
729729
raise xs_errors.XenError("TapdiskAlreadyRunning", "There is multiple minor for this tapdisk process")
730730
minor = l[0]["minor"]
731-
TapCtl.commit(pid, minor, QCOW2_TYPE, path) #TODO: Handle commit call failing
731+
TapCtl.commit(pid, minor, QCOW2_TYPE, path) #TODO: Handle commit call failing, it's needed if the tapdisk hasn't started yet or crashed
732732
#We need to wait for query to return concluded
733733
#TODO: We are technically ininterruptible since being interrupted will only stop checking if the job is done
734734
# We should call `tap-ctl cancel` if we are interrupted
@@ -742,11 +742,11 @@ def coalesce(self, path: str) -> int:
742742
util.SMlog("Got status {} for tapdisk {} minor: {}".format(status, pid, minor))
743743
return nb
744744
except TapCtl.CommandFailure as e:
745-
# util.SMlog("Query command failed on tapdisk instance {}. Retrying with offline coalesce...".format(pid))
746-
# return self.coalesce(path)
747745
util.SMlog("Query command failed on tapdisk instance {}. Raising...".format(pid))
748746
raise
749-
else:
747+
748+
@override
749+
def coalesce(self, path: str) -> int:
750750
allocated_blocks = self.getAllocatedSize(path)
751751
# -d on commit make it not empty the original image since we don't intend to keep it
752752
cmd = [QEMU_IMG, "commit", "-f", QCOW2_TYPE, path, "-d"]
@@ -822,3 +822,7 @@ def getKeyHash(self, path: str) -> Optional[str]:
822822
@override
823823
def setKey(self, path: str, key_hash: str) -> None:
824824
pass
825+
826+
@override
827+
def coalesceOnRemote(self) -> bool:
828+
return True

drivers/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2115,7 +2115,7 @@ def gen_path(path):
21152115
SMlog('* End profiling of {} ({}) *'.format(name, filename))
21162116

21172117

2118-
def strtobool(str):
2118+
def strtobool(str: str) -> bool:
21192119
# Note: `distutils` package is deprecated and slated for removal in Python 3.12.
21202120
# There is not alternative for strtobool.
21212121
# See: https://peps.python.org/pep-0632/#migration-advice

drivers/vhdutil.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,14 @@ def setKey(self, path: str, key_hash: str) -> None:
442442
"""
443443
self._ioretry([VHD_UTIL, "key", "-s", "-n", path, "-H", key_hash])
444444

445+
@override
446+
def coalesceOnRemote(self) -> bool:
447+
return False
448+
449+
@abstractmethod
450+
def coalesceOnline(self, path: str) -> int:
451+
raise NotImplementedError("Online coalesce not implemented for vhdutil")
452+
445453
@staticmethod
446454
def _convertAllocatedSizeToBytes(size: int):
447455
# Assume we have standard 2MB allocation blocks

0 commit comments

Comments
 (0)