cockpit/test/verify/check-storage-vdo

445 lines
16 KiB
Python
Executable File

#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)
# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
import re
from packagelib import PackageCase
from storagelib import StorageCase, StorageHelpers
from testlib import onlyImage, test_main
SIZE_10G = "10000000000"
class TestStorageVDO(StorageCase):
provision = {"0": {"memory_mb": 1800}}
def testVdo(self):
m = self.machine
b = self.browser
self.login_and_go("/storage")
# Make a volume group in which to create the VDO LV
m.add_disk(SIZE_10G, serial="DISK1")
b.wait_in_text("#drives", "DISK1")
m.execute("vgcreate vdo_vgroup /dev/sda")
b.wait_in_text("#devices", "vdo_vgroup")
b.click('.sidepanel-row:contains("vdo_vgroup")')
b.wait_in_text("#detail-content", "No logical volumes")
b.click("button:contains(Create new logical volume)")
self.dialog_wait_open()
b._wait_present("[data-field='purpose'] select option[value='block']")
# vdo only exists on RHEL
if not m.image.startswith("rhel") and not m.image.startswith("centos"):
b.wait_not_present("[data-field='purpose'] select option[value='vdo']")
return
# create VDO LV with default options and default virtual size
self.dialog_set_val("name", "vdo0")
self.dialog_set_val("purpose", "vdo")
self.dialog_set_val("vdo_psize", 6000)
self.dialog_apply()
self.dialog_wait_close()
# pool name gets auto-generated
pool_name = "vpool0"
self.content_row_wait_in_col(1, 1, "vdo0")
# the pool does not appear as a top-level volume
b.wait_not_in_text("#detail-content", pool_name)
# Volume tab
self.content_tab_wait_in_info(1, 1, "Name", "vdo0")
self.content_tab_wait_in_info(1, 1, "Size", "10 GB", "10.0 GB")
# VDO Pool tab
self.content_tab_wait_in_info(1, 2, "Name", pool_name)
self.content_tab_wait_in_info(1, 2, "Size", "6.00 GB")
# initial physical usage is ~ 4 GB, overhead for the deduplication index
self.content_tab_wait_in_info(1, 2, "Data used", "3.86 GB (64%)")
self.content_tab_wait_in_info(1, 2, "Metadata used", "0%")
b.wait_visible("input[aria-label='Use compression']:checked")
b.wait_visible("input[aria-label='Use deduplication']:checked")
# create a filesystem
self.content_row_action(1, "Format")
self.dialog({"type": "xfs",
"name": "vdofs",
"mount_point": "/run/data"})
self.content_row_wait_in_col(1, 2, "xfs filesystem")
# compressible data should affect logical usage
m.execute("dd if=/dev/zero of=/run/data/empty bs=1M count=1000")
self.content_row_wait_in_col(1, 4, "1.2 / 10 GB", alternate_val="1.2 / 9.9 GB")
# but not physical usage
self.content_tab_wait_in_info(1, 2, "Data used", "3.86 GB (64%)")
# incompressible data
m.execute("dd if=/dev/urandom of=/run/data/gibberish bs=1M count=1000")
self.content_row_wait_in_col(1, 4, "2.2 / 10 GB", alternate_val="2.2 / 9.9 GB")
# equal amount of physical space (not completely predictable due to random data)
self.content_tab_wait_in_info(1, 2, "Data used", "4.8", "4.9")
self.content_tab_wait_in_info(1, 2, "Data used", cond=lambda sel: re.search(r"\(8[1234]%\)", b.text(sel)))
def wait_prop(device, prop, value):
m.execute(f"until lvdisplay --noheadings -Co {prop} /dev/vdo_vgroup/{device} | grep -q '{value}'; do sleep 0.1; done")
# change compression/deduplication
b.click("input[aria-label='Use compression']")
b.wait_visible("input[aria-label='Use compression']:not(checked):not([disabled])")
wait_prop(pool_name, "vdo_compression_state", "offline")
b.click("input[aria-label='Use compression']")
b.wait_visible("input[aria-label='Use compression']:checked:not([disabled])")
wait_prop(pool_name, "vdo_compression_state", "online")
b.click("input[aria-label='Use deduplication']")
b.wait_visible("input[aria-label='Use deduplication']:not(checked):not([disabled])")
wait_prop(pool_name, "vdo_index_state", r"offline\|closed")
b.click("input[aria-label='Use deduplication']")
b.wait_visible("input[aria-label='Use deduplication']:checked:not([disabled])")
wait_prop(pool_name, "vdo_index_state", "online")
# grow volume
self.content_tab_action(1, 1, "Grow")
self.dialog({"size": 12000})
self.content_tab_wait_in_info(1, 1, "Size", "12.0 GB")
wait_prop("vdo0", "lv_size", "11.18g")
# grow pool
self.content_tab_action(1, 2, "Grow")
self.dialog({"size": 8000})
self.content_tab_wait_in_info(1, 2, "Size", "8.15 GB")
wait_prop(pool_name, "lv_size", "7.59g")
# deleting the vdo0 volume deletes the pool as well
self.content_dropdown_action(1, "Delete")
self.confirm()
b.wait_in_text("#detail-content", "No logical volumes")
self.assertEqual(m.execute("lvs --noheadings").strip(), "")
# create VDO LV with customized options
b.click("button:contains(Create new logical volume)")
self.dialog_wait_open()
b._wait_present("[data-field='purpose'] select option[value='block']")
self.dialog_set_val("name", "vdo0")
self.dialog_set_val("purpose", "vdo")
self.dialog_set_val("vdo_psize", 6000)
# grossly overcommitted
self.dialog_set_val("vdo_lsize", 20000)
self.dialog_set_val("vdo_options.compression", False)
self.dialog_apply()
self.dialog_wait_close()
self.content_row_wait_in_col(1, 1, "vdo0")
# Volume tab
self.content_tab_wait_in_info(1, 1, "Name", "vdo0")
self.content_tab_wait_in_info(1, 1, "Size", "20.0 GB")
# VDO Pool tab
self.content_tab_wait_in_info(1, 2, "Size", "6.0 GB", "6.00 GB")
b.wait_visible("input[aria-label='Use compression']:not(:checked)")
b.wait_visible("input[aria-label='Use deduplication']:checked")
wait_prop(pool_name, "vdo_compression_state", "offline")
wait_prop(pool_name, "vdo_index_state", "online")
# delete again
self.content_dropdown_action(1, "Delete")
self.confirm()
b.wait_in_text("#detail-content", "No logical volumes")
self.assertEqual(m.execute("lvs --noheadings").strip(), "")
# react to CLI
m.execute("lvcreate --type vdo --size 6g --virtualsize 10g --name vdo1 --yes vdo_vgroup")
self.content_row_wait_in_col(1, 1, "vdo1")
m.execute("lvremove --yes /dev/vdo_vgroup/vdo1")
b.wait_in_text("#detail-content", "No logical volumes")
@onlyImage("legacy VDO API only supported on RHEL 8", "rhel-8*", "centos-8*")
class TestStorageLegacyVDO(StorageCase):
def testVdo(self):
m = self.machine
b = self.browser
self.login_and_go("/storage")
b.wait_visible("#devices")
# Make a logical volume for use as the backing device.
m.add_disk(SIZE_10G, serial="DISK1")
b.wait_in_text("#drives", "DISK1")
m.execute("vgcreate vdo_vgroup /dev/sda; lvcreate -n lvol -L 5G vdo_vgroup")
# Create VDO; this is not supported any more, thus no UI for it
m.execute("vdo create --device /dev/vdo_vgroup/lvol --name vdo0 --vdoLogicalSize 5G", timeout=300)
b.wait_in_text("#devices", "vdo0")
b.click("#devices .sidepanel-row:contains(vdo0)")
b.wait_visible("#storage-detail")
def detail(index):
return f'#detail-header .pf-v5-c-description-list__group:nth-of-type({index}) > dd'
b.wait_text(detail(1), "/dev/mapper/vdo0")
b.wait_in_text(detail(2), "vdo_vgroup")
b.wait_in_text(detail(3), "used of 5.37 GB")
b.wait_in_text(detail(4), "used of 5.37 GB")
b.wait_text(detail(5), "268 MB")
b.wait_visible(detail(6) + " input:checked")
b.wait_visible(detail(7) + " input:checked")
# Make a filesystem on it
self.content_row_wait_in_col(1, 2, "Unrecognized data")
self.content_row_action(1, "Format")
self.dialog({"type": "xfs",
"name": "FILESYSTEM",
"mount_point": "/run/data"})
self.content_row_wait_in_col(1, 2, "xfs filesystem")
# _netdev etc should have been prefilled
self.content_tab_wait_in_info(1, 1, "Mount point", "after network")
self.content_tab_wait_in_info(1, 1, "Mount point", "x-systemd.device-timeout=0")
self.content_tab_wait_in_info(1, 1, "Mount point", "x-systemd.requires=vdo.service")
self.content_row_wait_in_col(1, 4, "/ 5.4 GB")
# Grow physical
m.execute("lvresize vdo_vgroup/lvol -L 9G")
b.wait_in_text(".pf-v5-c-alert__description", 'Only 5.37 GB of 9')
b.click("button:contains('Grow to take all space')")
b.wait_not_present(".pf-v5-c-alert")
b.wait_in_text(detail(3), "used of 9.66 GB")
# Grow logical
b.click(detail(4) + " button:contains(Grow)")
self.dialog({"lsize": 10000})
b.wait_in_text(detail(4), "used of 10.0 GB")
self.content_row_wait_in_col(1, 4, "/ 10 GB")
# Stop
b.wait_visible('#detail-content table')
b.click('.pf-v5-c-card__header:contains("VDO") button:contains("Stop")')
self.dialog_wait_open()
b.wait_in_text("#dialog", "unmount, stop")
self.dialog_apply()
self.dialog_wait_close()
b.wait_not_present('#detail-content table')
# Delete
b.click('.pf-v5-c-card__header:contains("VDO") button:contains("Delete")')
self.dialog_wait_open()
self.dialog_apply_with_retry(expected_errors=["Device or resource busy"])
b.wait_visible("#storage")
b.wait_not_in_text("#devices", "vdo0")
def testBrokenVdo(self):
m = self.machine
b = self.browser
self.login_and_go("/storage")
b.wait_visible("#devices")
m.add_disk(SIZE_10G, serial="DISK1")
b.wait_in_text("#drives", "DISK1")
# Install a valid configuration file that describes a broken VDO
m.write("/etc/vdoconf.yml", """
config: !Configuration
vdos:
vdo0: !VDOService
_operationState: beginCreate
ackThreads: 1
activated: enabled
bioRotationInterval: 64
bioThreads: 4
blockMapCacheSize: 128M
blockMapPeriod: 16380
compression: enabled
cpuThreads: 2
deduplication: enabled
device: /dev/sda
hashZoneThreads: 1
indexCfreq: 0
indexMemory: 0.25
indexSparse: disabled
indexThreads: 0
logicalBlockSize: 4096
logicalSize: 10G
logicalThreads: 1
name: vdo0
physicalSize: 10G
physicalThreads: 1
readCache: disabled
readCacheSize: 0M
slabSize: 2G
writePolicy: sync
version: 538380551
""")
b.wait_in_text("#devices", "vdo0")
b.click("#devices .sidepanel-row:contains(vdo0)")
b.click("#storage-detail .pf-m-danger button:contains('Remove device')")
b.wait_visible("#storage")
b.wait_not_in_text("#devices", "vdo0")
def testBrokenVdoConfig(self):
m = self.machine
b = self.browser
self.login_and_go("/storage")
b.wait_visible("#devices")
# Install a valid configuration file
m.write("/etc/vdoconf.yml", """
config: !Configuration
vdos:
vdo0: !VDOService
_operationState: finished
ackThreads: 1
activated: enabled
bioRotationInterval: 64
bioThreads: 4
blockMapCacheSize: 128M
blockMapPeriod: 16380
compression: enabled
cpuThreads: 2
deduplication: enabled
device: /dev/sda
hashZoneThreads: 1
indexCfreq: 0
indexMemory: 0.25
indexSparse: disabled
indexThreads: 0
logicalBlockSize: 4096
logicalSize: 10G
logicalThreads: 1
name: vdo0
physicalSize: 10G
physicalThreads: 1
readCache: disabled
readCacheSize: 0M
slabSize: 2G
writePolicy: sync
version: 538380551
""")
b.wait_in_text("#devices", "vdo0")
# Install a broken configuration file
m.write("/etc/vdoconf.yml", """
config: !Configuration
vdos:
vdo0: !VDOService
blah: 12
""")
b.wait_not_in_text("#devices", "vdo0")
# Install a valid configuration file again
m.write("/etc/vdoconf.yml", """
config: !Configuration
vdos:
vdo1: !VDOService
_operationState: finished
ackThreads: 1
activated: enabled
bioRotationInterval: 64
bioThreads: 4
blockMapCacheSize: 128M
blockMapPeriod: 16380
compression: enabled
cpuThreads: 2
deduplication: enabled
device: /dev/sda
hashZoneThreads: 1
indexCfreq: 0
indexMemory: 0.25
indexSparse: disabled
indexThreads: 0
logicalBlockSize: 4096
logicalSize: 10G
logicalThreads: 1
name: vdo1
physicalSize: 10G
physicalThreads: 1
readCache: disabled
readCacheSize: 0M
slabSize: 2G
writePolicy: sync
version: 538380551
""")
b.wait_in_text("#devices", "vdo1")
@onlyImage("VDO API only supported on RHEL", "rhel-*", "centos-*")
class TestStoragePackagesVDO(PackageCase, StorageHelpers):
provision = {"0": {"memory_mb": 1500}}
def testVdoMissingPackages(self):
m = self.machine
b = self.browser
m.execute("pkcon remove -y vdo")
m.execute("pkcon refresh")
self.login_and_go("/storage")
m.add_disk(SIZE_10G, serial="DISK1")
b.wait_in_text("#drives", "DISK1")
m.execute("vgcreate vdo_vgroup /dev/sda")
b.wait_in_text("#devices", "vdo_vgroup")
b.click('.sidepanel-row:contains("vdo_vgroup")')
b.click("button:contains(Create new logical volume)")
self.dialog_wait_open()
b._wait_present("[data-field='purpose'] select option[value='block']")
# no package installation helper text
self.assertFalse(b.is_present("#dialog .pf-v5-c-helper-text"))
self.dialog_set_val("purpose", "vdo")
# shows the package installation note
b.wait_in_text("#dialog .pf-v5-c-helper-text", "vdo package will be installed")
# vdo package does not exist
self.dialog_apply()
b.wait_in_text("#dialog .pf-v5-c-alert.pf-m-danger", "vdo is not available from any repository")
self.createPackage("vdo", "999", "1")
self.enableRepo()
self.dialog_apply()
# gets over package installation now, but it's a mock package
b.wait_in_text("#dialog .pf-v5-c-alert.pf-m-danger", "vdoformat")
b.wait_in_text("#dialog .pf-v5-c-alert.pf-m-danger", "No such file or directory")
# but it got past package installation
self.assertIn("999", m.execute("rpm -q vdo"))
self.dialog_cancel()
if __name__ == '__main__':
test_main()