445 lines
16 KiB
Python
Executable File
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()
|