|
2 | 2 | import pytest
|
3 | 3 | import time
|
4 | 4 |
|
5 |
| -from .conftest import GROUP_NAME, LINSTOR_PACKAGE |
| 5 | +from .conftest import GROUP_NAME, LINSTOR_PACKAGE, LINSTOR_RELEASE_PACKAGE |
6 | 6 | from lib.commands import SSHCommandFailed
|
7 | 7 | from lib.common import wait_for, vm_image
|
8 | 8 | from tests.storage import vdi_is_open
|
@@ -143,6 +143,108 @@ def test_linstor_sr_expand_disk(self, linstor_sr, provisioning_type, storage_poo
|
143 | 143 | # Ensure VM is able to start and shutdown on expanded SR
|
144 | 144 | self.test_start_and_shutdown_VM(vm)
|
145 | 145 |
|
| 146 | + @pytest.mark.small_vm |
| 147 | + def test_linstor_sr_expand_host(self, linstor_sr, host, hostB1, provisioning_type, |
| 148 | + storage_pool_name, vm_on_linstor_sr): |
| 149 | + """ |
| 150 | + This test validates expansion of a LINSTOR SR by dynamically adding a new host with local storage to the pool. |
| 151 | + A VM is started on the SR before expansion begins to ensure the SR is in active use during the process. |
| 152 | +
|
| 153 | + It performs the following steps: |
| 154 | + - Installs LINSTOR packages on the new host (if missing). |
| 155 | + - Detects and prepares raw disks using LVM commands. |
| 156 | + - Joins the host (hostB1) to the existing pool and registers it with LINSTOR as a node. |
| 157 | + - Creates a new LINSTOR storage pool on the added host (LVM or LVM-thin, based on provisioning type). |
| 158 | + - Confirms SR expansion by verifying increased physical size. |
| 159 | + - Ensures SR functionality by rebooting the VM running on the SR. |
| 160 | +
|
| 161 | + Finally, the test cleans up by deleting the LINSTOR node, ejecting the host from the pool, |
| 162 | + and removing packages and LVM metadata. |
| 163 | + """ |
| 164 | + sr = linstor_sr |
| 165 | + vm = vm_on_linstor_sr |
| 166 | + vm.start() |
| 167 | + sr_size = sr.pool.master.xe('sr-param-get', {'uuid': sr.uuid, 'param-name': 'physical-size'}) |
| 168 | + resized = False |
| 169 | + # Ensure that its a single host pool and not multi host pool |
| 170 | + assert len(hostB1.pool.hosts) == 1, "This test requires second host to be a single host" |
| 171 | + # Ensure that the host has disks available to use, we do not care about disks symmetry across pool |
| 172 | + available_disks = hostB1.available_disks() |
| 173 | + # We need the disk to be "raw" (non LVM_member etc) to use |
| 174 | + available_disks = [disk for disk in available_disks if hostB1.raw_disk_is_available(disk)] |
| 175 | + assert len(available_disks) >= 1, "This test requires second host to have free disk(s)" |
| 176 | + if not hostB1.is_package_installed(LINSTOR_PACKAGE): |
| 177 | + logging.info("Installing %s on host %s", LINSTOR_PACKAGE, hostB1) |
| 178 | + hostB1.yum_install([LINSTOR_RELEASE_PACKAGE]) |
| 179 | + hostB1.yum_install([LINSTOR_PACKAGE], enablerepo="xcp-ng-linstor-testing") |
| 180 | + # Needed because the linstor driver is not in the xapi sm-plugins list |
| 181 | + # before installing the LINSTOR packages. |
| 182 | + hostB1.ssh(["systemctl", "restart", "multipathd"]) |
| 183 | + hostB1.restart_toolstack(verify=True) |
| 184 | + |
| 185 | + devices = [f"/dev/{disk}" for disk in available_disks] |
| 186 | + |
| 187 | + for disk in available_disks: |
| 188 | + logging.info("Found Disk %s", disk) |
| 189 | + device = "/dev/" + disk |
| 190 | + hostB1.ssh(['pvcreate', device]) |
| 191 | + |
| 192 | + hostB1.ssh(['vgcreate', GROUP_NAME] + devices) |
| 193 | + |
| 194 | + sr_group_name = "xcp-sr-" + storage_pool_name.replace("/", "_") |
| 195 | + hostname_hostB1 = hostB1.xe('host-param-get', {'uuid': hostB1.uuid, |
| 196 | + 'param-name': 'name-label'}) |
| 197 | + |
| 198 | + controller_option = "--controllers=" |
| 199 | + for member in host.pool.hosts: |
| 200 | + controller_option += f"{member.hostname_or_ip}," |
| 201 | + |
| 202 | + hostB1_pool = hostB1.pool # Saving the hostB1 pool info before overwrite in join_pool. |
| 203 | + try: |
| 204 | + logging.info("Joining host %s to pool %s", hostB1, host) |
| 205 | + # This will cause hostB1 pool to overwrite itself as host.pool creating issues on next run. |
| 206 | + hostB1.join_pool(host.pool) |
| 207 | + logging.info("Current list of linstor nodes:") |
| 208 | + logging.info(host.ssh_with_result(["linstor", controller_option, "node", "list"]).stdout) |
| 209 | + logging.info("Creating linstor node") |
| 210 | + host.ssh(["linstor", controller_option, "node", "create", "--node-type", "combined", |
| 211 | + "--communication-type", "plain", hostname_hostB1, hostB1.hostname_or_ip]) # Linstor Node Create |
| 212 | + logging.info(hostB1.ssh_with_result(['systemctl', 'restart', 'linstor-satellite.service']).stdout) |
| 213 | + time.sleep(45) # Wait for node to come online |
| 214 | + logging.info("New list of linstor nodes:") |
| 215 | + logging.info(host.ssh_with_result(["linstor", controller_option, "node", "list"]).stdout) |
| 216 | + logging.info("Expanding with linstor node") |
| 217 | + |
| 218 | + if provisioning_type == "thin": |
| 219 | + hostB1.ssh(['lvcreate', '-l', '+100%FREE', '-T', storage_pool_name]) |
| 220 | + host.ssh_with_result(["linstor", controller_option, "storage-pool", "create", "lvmthin", |
| 221 | + hostname_hostB1, sr_group_name, storage_pool_name]).stdout # Expand linstor |
| 222 | + else: |
| 223 | + host.ssh_with_result(["linstor", controller_option, "storage-pool", "create", "lvm", |
| 224 | + hostname_hostB1, sr_group_name, storage_pool_name]).stdout # Expand linstor |
| 225 | + except Exception as e: |
| 226 | + logging.info("Exception: {}".format(e)) |
| 227 | + host.ssh(["linstor", controller_option, "node", "delete", hostname_hostB1]) # Linstor Node Delete |
| 228 | + host.pool.eject_host(hostB1) |
| 229 | + hostB1.ssh(['vgremove', '-y', GROUP_NAME]) |
| 230 | + hostB1.ssh(['pvremove', '-y'] + devices) # Device cleanup |
| 231 | + hostB1.yum_remove([LINSTOR_PACKAGE]) # Package cleanup |
| 232 | + |
| 233 | + resized = True |
| 234 | + sr.scan() |
| 235 | + new_sr_size = sr.pool.master.xe('sr-param-get', {'uuid': sr.uuid, 'param-name': 'physical-size'}) |
| 236 | + assert int(new_sr_size) > int(sr_size) and resized is True, \ |
| 237 | + f"Expected SR size to increase but got old size: {sr_size}, new size: {new_sr_size}" |
| 238 | + logging.info("SR expansion completed from size %s to %s", sr_size, new_sr_size) |
| 239 | + vm.shutdown(verify=True) |
| 240 | + # Ensure VM is able to start and shutdown on expanded SR |
| 241 | + self.test_start_and_shutdown_VM(vm) |
| 242 | + |
| 243 | + host.ssh_with_result(["linstor", controller_option, "node", "delete", hostname_hostB1]).stdout |
| 244 | + host.pool.eject_host(hostB1) |
| 245 | + hostB1.pool = hostB1_pool # Post eject, reset hostB1.pool for next run ("thick") |
| 246 | + hostB1.yum_remove([LINSTOR_PACKAGE]) # Package cleanup |
| 247 | + |
146 | 248 | # *** tests with reboots (longer tests).
|
147 | 249 |
|
148 | 250 | @pytest.mark.reboot
|
|
0 commit comments