Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate rootdisk xfs uuid rewriting. #14

Merged
merged 1 commit into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/fc/qemu/hazmat/ceph.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def ensure_root_volume(self):
self.log.info("create-vm")
cmd(self.CREATE_VM.format(**self.cfg), self.log)
self.root.lock()
self.root.regen_xfs_uuid()

def ensure_swap_volume(self):
self.log.info("ensure-swap")
Expand Down
56 changes: 50 additions & 6 deletions src/fc/qemu/hazmat/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

import rbd

from ..util import cmd, remove_empty_dirs
from ..timeout import TimeOut, TimeoutError
from ..util import cmd, parse_export_format, remove_empty_dirs


class Image(object):
Expand All @@ -26,6 +27,7 @@ def __init__(self, ceph, name):
self.ceph = ceph
self.ioctx = ceph.ioctx
self.name = name
self.log = ceph.log.bind(image=self.name)
self.rbd = rbd.RBD()

def __str__(self):
Expand All @@ -45,6 +47,12 @@ def part1dev(self):
return None
return self.device + "-part1"

def wait_for_part1dev(self):
timeout = TimeOut(5, interval=0.1, raise_on_timeout=True, log=self.log)
while timeout.tick():
if os.path.exists(self.part1dev):
break

def map(self):
if self.device is not None:
return
Expand All @@ -53,8 +61,10 @@ def map(self):
self.ceph.CEPH_CONF, self.ceph.CEPH_CLIENT, self.fullname
)
)
time.sleep(0.1)
self.device = "/dev/rbd/" + self.fullname
device = "/dev/rbd/" + self.fullname
while not os.path.exists(device):
time.sleep(0.1)
self.device = device

def unmap(self):
if self.device is None:
Expand All @@ -69,6 +79,11 @@ def unmap(self):
@contextlib.contextmanager
def mapped(self):
"""Maps the image to a block device and yields the device name."""
if self.device:
# Re-entrant version - do not map/unmap
yield self.device
return
# Non-reentrant version: actually do the work
self.map()
try:
yield self.device
Expand All @@ -87,6 +102,7 @@ def mount(self):
os.makedirs(mountpoint)
except OSError: # pragma: no cover
pass
self.wait_for_part1dev()
self.cmd('mount "{}" "{}"'.format(self.part1dev, mountpoint))
self.mountpoint = mountpoint

Expand Down Expand Up @@ -351,16 +367,44 @@ def mkfs(self, fstype="xfs", gptbios=False):
)
)
self.cmd("partprobe {}".format(self.device))
time.sleep(0.2)
while not p.exists(self.part1dev): # pragma: no cover
time.sleep(0.1)
self.wait_for_part1dev()
options = getattr(self.ceph, "MKFS_" + fstype.upper())
self.cmd(
self.MKFS_CMD[fstype].format(
options=options, device=self.part1dev, label=self.label
)
)

def regen_xfs_uuid(self):
"""Regenerate the UUID of the XFS filesystem on partition 1."""
with self.mapped():
try:
self.wait_for_part1dev()
except TimeoutError:
self.log.warn(
"regenerate-xfs-uuid",
status="skipped",
reason="no partition found",
)
return
output = self.cmd(f"blkid {self.part1dev} -o export")
values = parse_export_format(output)
fs_type = values.get("TYPE")
if fs_type != "xfs":
self.log.info(
"regenerate-xfs-uuid",
device=self.part1dev,
status="skipped",
fs_type=fs_type,
reason="filesystem type != xfs",
)
return
with self.mounted():
# Mount once to ensure a clean log.
pass
self.log.info("regenerate-xfs-uuid", device=self.part1dev)
self.cmd(f"xfs_admin -U generate {self.part1dev}")

def seed(self, enc, generation):
self.log.info("seed")
with self.mounted() as target:
Expand Down
15 changes: 15 additions & 0 deletions src/fc/qemu/tests/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from fc.qemu.util import parse_export_format


def test_export_format():
data = """DEVNAME="test"
UUID='a5370804-c9b1-4610-8f99-02a7841b8393'
BLOCK_SIZE=512
TYPE=xfs
"""
assert parse_export_format(data) == {
"BLOCK_SIZE": "512",
"TYPE": "xfs",
"UUID": "a5370804-c9b1-4610-8f99-02a7841b8393",
"DEVNAME": "test",
}
25 changes: 25 additions & 0 deletions src/fc/qemu/tests/vm_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,30 @@ def test_simple_vm_lifecycle_start_stop(vm):
fc-create-vm> --------
/nix/store/.../bin/fc-create-vm machine=simplevm returncode=0 subsystem=ceph
lock machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
rbd args=-c "/etc/ceph/ceph.conf" --id "host1" map "rbd.ssd/simplevm.root" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
rbd> /dev/rbd0
rbd machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
waiting interval=0 machine=simplevm remaining=4 subsystem=ceph volume=rbd.ssd/simplevm.root
blkid args=/dev/rbd/rbd.ssd/simplevm.root-part1 -o export machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
blkid> DEVNAME=/dev/rbd/rbd.ssd/simplevm.root-part1
blkid> UUID=...
blkid> BLOCK_SIZE=512
blkid> TYPE=xfs
blkid> PARTLABEL=ROOT
blkid> PARTUUID=...
blkid machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
mount args="/dev/rbd/rbd.ssd/simplevm.root-part1" "/mnt/rbd/rbd.ssd/simplevm.root" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
mount machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
umount args="/mnt/rbd/rbd.ssd/simplevm.root" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
umount machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
regenerate-xfs-uuid device=/dev/rbd/rbd.ssd/simplevm.root-part1 machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
xfs_admin args=-U generate /dev/rbd/rbd.ssd/simplevm.root-part1 machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
xfs_admin> Clearing log and setting UUID
xfs_admin> writing all SBs
xfs_admin> new UUID = ...
xfs_admin machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
rbd args=-c "/etc/ceph/ceph.conf" --id "host1" unmap "/dev/rbd/rbd.ssd/simplevm.root" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.root
rbd machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.root
ensure-tmp machine=simplevm subsystem=ceph
lock machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.tmp
rbd args=-c "/etc/ceph/ceph.conf" --id "host1" map "rbd.ssd/simplevm.tmp" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.tmp
Expand All @@ -99,6 +123,7 @@ def test_simple_vm_lifecycle_start_stop(vm):
sgdisk machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.tmp
partprobe args=/dev/rbd/rbd.ssd/simplevm.tmp machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.tmp
partprobe machine=simplevm returncode=0 subsystem=ceph volume=rbd.ssd/simplevm.tmp
waiting interval=0 machine=simplevm remaining=4 subsystem=ceph volume=rbd.ssd/simplevm.tmp
mkfs.xfs args=-q -f -K -m crc=1,finobt=1 -d su=4m,sw=1 -L "tmp" "/dev/rbd/rbd.ssd/simplevm.tmp-part1" machine=simplevm subsystem=ceph volume=rbd.ssd/simplevm.tmp
mkfs.xfs> mkfs.xfs: Specified data stripe unit 8192 is not the same as the volume stripe unit 128
mkfs.xfs> mkfs.xfs: Specified data stripe width 8192 is not the same as the volume stripe width 128
Expand Down
24 changes: 24 additions & 0 deletions src/fc/qemu/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import subprocess
import sys
import time
from typing import Dict

from structlog import get_logger

Expand Down Expand Up @@ -145,3 +146,26 @@ def ensure_separate_cgroup():
# Keep going.
with open("{}/cgroup.procs".format(CGROUP), "w") as f:
f.write(str(os.getpid()))


def parse_export_format(data: str) -> Dict[str, str]:
"""Parses formats intended for shell exports into a dict.

ASDF=foo
BSDF=bar

Introduced to support output from `blkid`.

"""
result = {}
for line in data.splitlines():
try:
k, v = line.strip().split("=")
except ValueError:
continue
k = k.strip()
if not k:
continue
v = v.strip("'\"")
result[k] = v
return result
Loading