if not self.mapped:
raise CephTestException("Unable to create fs, image not mapped.")
+ LOG.info("Initializing fs, image: %s.", self.name)
+
self._init_disk()
self._create_partition()
self._format_volume()
return int(result.stdout.decode().strip())
+ @Tracer.trace
+ def resize(self, new_size_mb, allow_shrink=False):
+ LOG.info(
+ "Resizing image: %s. New size: %s MB, old size: %s MB",
+ self.name, new_size_mb, self.size_mb)
+
+ cmd = ["rbd", "resize", self.name,
+ "--size", f"{new_size_mb}M", "--no-progress"]
+ if allow_shrink:
+ cmd.append("--allow-shrink")
+
+ execute(*cmd)
+
+ self.size_mb = new_size_mb
+
+ @Tracer.trace
+ def get_disk_size(self):
+ """Retrieve the virtual disk size (bytes) reported by Windows."""
+ cmd = f"(Get-Disk -Number {self.disk_number}).Size"
+ result = ps_execute(cmd)
+
+ disk_size = result.stdout.decode().strip()
+ if not disk_size.isdigit():
+ raise CephTestException(
+ "Invalid disk size received: %s" % disk_size)
+
+ return int(disk_size)
+
+ @Tracer.trace
+ @retry_decorator(timeout=30)
+ def wait_for_disk_resize(self):
+ # After resizing the rbd image, the daemon is expected to receive
+ # the notification, inform the WNBD driver and then trigger a disk
+ # rescan (IOCTL_DISK_UPDATE_PROPERTIES). This might take a few seconds,
+ # so we'll need to do some polling.
+ disk_size = self.get_disk_size()
+ disk_size_mb = disk_size // (1 << 20)
+
+ if disk_size_mb != self.size_mb:
+ raise CephTestException(
+ "The disk size hasn't been updated yet. Retrieved size: "
+ f"{disk_size_mb}MB. Expected size: {self.size_mb}MB.")
+
class RbdTest(object):
image: RbdImage
return self.image.path
@Tracer.trace
- def run(self):
+ def _run_fio(self, fio_size_mb=None):
LOG.info("Starting FIO test.")
cmd = [
"fio", "--thread", "--output-format=json",
"--randrepeat=%d" % self.iterations,
"--direct=1", "--gtod_reduce=1", "--name=test",
"--bs=%s" % self.bs, "--iodepth=%s" % self.iodepth,
- "--size=%sM" % self.fio_size_mb,
+ "--size=%sM" % (fio_size_mb or self.fio_size_mb),
"--readwrite=%s" % self.op,
"--numjobs=%s" % self.workers,
"--filename=%s" % self._get_fio_path(),
LOG.info("Completed FIO test.")
self.process_result(result.stdout)
+ @Tracer.trace
+ def run(self):
+ self._run_fio()
+
@classmethod
def print_results(cls,
title: str = "Benchmark results",
print(table)
+class RbdResizeFioTest(RbdFioTest):
+ """Image resize test.
+
+ This test extends and then shrinks the image, performing FIO tests to
+ validate the resized image.
+ """
+
+ @Tracer.trace
+ def run(self):
+ self.image.resize(self.image_size_mb * 2)
+ self.image.wait_for_disk_resize()
+
+ self._run_fio(fio_size_mb=self.image_size_mb * 2)
+
+ self.image.resize(self.image_size_mb // 2, allow_shrink=True)
+ self.image.wait_for_disk_resize()
+
+ self._run_fio(fio_size_mb=self.image_size_mb // 2)
+
+ # Just like rbd-nbd, rbd-wnbd is masking out-of-bounds errors.
+ # For this reason, we don't have a negative test that writes
+ # passed the disk boundary.
+
+
class RbdFsFioTest(RbdFsTestMixin, RbdFioTest):
def initialize(self):
super(RbdFsFioTest, self).initialize()
time.sleep(self._rand_float(0, 5))
stamp = self._read_stamp()
- assert(stamp == b'\0' * len(self._get_stamp()))
+ assert stamp == b'\0' * len(self._get_stamp())
self._write_stamp()
stamp = self._read_stamp()
- assert(stamp == self._get_stamp())
+ assert stamp == self._get_stamp()
class RbdFsStampTest(RbdFsTestMixin, RbdStampTest):
TESTS: typing.Dict[str, typing.Type[RbdTest]] = {
'RbdTest': RbdTest,
'RbdFioTest': RbdFioTest,
+ 'RbdResizeFioTest': RbdResizeFioTest,
'RbdStampTest': RbdStampTest,
# FS tests
'RbdFsTest': RbdFsTest,