This is an automated email from the ASF dual-hosted git repository.
pearl11594 pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push:
new 217e5344461 linstor: improve integration-tests (#10439)
217e5344461 is described below
commit 217e5344461b4671b40b3cde016a2528f42ff5c2
Author: Rene Peinthor <[email protected]>
AuthorDate: Mon Mar 3 17:33:28 2025 +0100
linstor: improve integration-tests (#10439)
make tests easier to run and without modifying the test to match the
correct system. Also add tests for volume and vm instance snapshots.
Improve runtime by ~6 minutes.
---
.../plugins/linstor/test_linstor_volumes.py | 252 ++++++++++++++++++---
1 file changed, 218 insertions(+), 34 deletions(-)
diff --git a/test/integration/plugins/linstor/test_linstor_volumes.py
b/test/integration/plugins/linstor/test_linstor_volumes.py
index 60dd84a13b1..e43f4e923e1 100644
--- a/test/integration/plugins/linstor/test_linstor_volumes.py
+++ b/test/integration/plugins/linstor/test_linstor_volumes.py
@@ -18,14 +18,15 @@
import logging
import random
import time
+import socket
# All tests inherit from cloudstackTestCase
from marvin.cloudstackTestCase import cloudstackTestCase
# Import Integration Libraries
# base - contains all resources as entities and defines create, delete, list
operations on them
-from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot,
StoragePool, Template, User, \
- VirtualMachine, Volume
+from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot,
StoragePool, Template, User
+from marvin.lib.base import VirtualMachine, Volume, VmSnapshot
# common - commonly used methods for all tests are listed here
from marvin.lib.common import get_domain, get_template, get_zone,
list_clusters, list_hosts, list_virtual_machines, \
@@ -97,8 +98,7 @@ class TestData:
# hypervisor type to test
hypervisor_type = kvm
- def __init__(self):
- linstor_controller_url = "http://10.43.224.8"
+ def __init__(self, linstor_controller_url):
self.testdata = {
TestData.kvm: {
TestData.username: "admin",
@@ -197,7 +197,7 @@ class TestData:
"resourceGroup": "acs-test-same"
}
},
- # Linstor storage pool on different ScaleIO storage instance
+ # Linstor storage pool on different Linstor storage instance
TestData.primaryStorageDistinctInstance: {
"name": "Linstor-%d" % random.randint(0, 100),
TestData.scope: "ZONE",
@@ -225,6 +225,44 @@ class TestData:
},
}
+class ServiceReady:
+ @classmethod
+ def ready(cls, hostname: str, port: int) -> bool:
+ try:
+ s = socket.create_connection((hostname, port), timeout=1)
+ s.close()
+ return True
+ except (ConnectionRefusedError, socket.timeout, OSError):
+ return False
+
+ @classmethod
+ def wait(
+ cls,
+ hostname,
+ port,
+ wait_interval = 5,
+ timeout = 90,
+ service_name = 'ssh') -> bool:
+ """
+ Wait until the controller can be reached.
+ :param hostname:
+ :param port: port of the application
+ :param wait_interval:
+ :param timeout: time to wait until exit with False
+ :param service_name: name of the service to wait
+ :return:
+ """
+ starttime = int(round(time.time() * 1000))
+ while not cls.ready(hostname, port):
+ if starttime + timeout * 1000 < int(round(time.time() * 1000)):
+ raise RuntimeError("{s} {h} cannot be
reached.".format(s=service_name, h=hostname))
+ time.sleep(wait_interval)
+ return True
+
+ @classmethod
+ def wait_ssh_ready(cls, hostname, wait_interval = 1, timeout = 90):
+ return cls.wait(hostname, 22, wait_interval, timeout, "ssh")
+
class TestLinstorVolumes(cloudstackTestCase):
_volume_vm_id_and_vm_id_do_not_match_err_msg = "The volume's VM ID and the
VM's ID do not match."
@@ -239,7 +277,11 @@ class TestLinstorVolumes(cloudstackTestCase):
cls.apiClient = testclient.getApiClient()
cls.configData = testclient.getParsedTestDataConfig()
cls.dbConnection = testclient.getDbConnection()
- cls.testdata = TestData().testdata
+
+ # first host has the linstor controller
+ first_host = list_hosts(cls.apiClient)[0]
+
+ cls.testdata = TestData(first_host.ipaddress).testdata
# Get Resources from Cloud Infrastructure
cls.zone = get_zone(cls.apiClient,
zone_id=cls.testdata[TestData.zoneId])
@@ -326,7 +368,8 @@ class TestLinstorVolumes(cloudstackTestCase):
serviceofferingid=cls.compute_offering.id,
templateid=cls.template.id,
domainid=cls.domain.id,
- startvm=False
+ startvm=False,
+ mode='basic',
)
TestLinstorVolumes._start_vm(cls.virtual_machine)
@@ -394,7 +437,8 @@ class TestLinstorVolumes(cloudstackTestCase):
serviceofferingid=self.compute_offering.id,
templateid=self.template.id,
domainid=self.domain.id,
- startvm=False
+ startvm=False,
+ mode='basic',
)
TestLinstorVolumes._start_vm(test_virtual_machine)
@@ -887,8 +931,31 @@ class TestLinstorVolumes(cloudstackTestCase):
"Check volume was deleted"
)
+ @attr(tags=['basic'], required_hardware=False)
+ def test_09_create_snapshot(self):
+ """Create snapshot of root disk"""
+ self.virtual_machine.stop(self.apiClient)
+
+ volume = list_volumes(
+ self.apiClient,
+ virtualmachineid = self.virtual_machine.id,
+ type = "ROOT",
+ listall = True,
+ )
+ snapshot = Snapshot.create(
+ self.apiClient,
+ volume_id = volume[0].id,
+ account=self.account.name,
+ domainid=self.domain.id,
+ )
+
+ self.assertIsNotNone(snapshot, "Could not create snapshot")
+
+ snapshot.delete(self.apiClient)
+
+
@attr(tags=['advanced', 'migration'], required_hardware=False)
- def test_09_migrate_volume_to_same_instance_pool(self):
+ def test_10_migrate_volume_to_same_instance_pool(self):
"""Migrate volume to the same instance pool"""
if not self.testdata[TestData.migrationTests]:
@@ -906,7 +973,8 @@ class TestLinstorVolumes(cloudstackTestCase):
serviceofferingid=self.compute_offering.id,
templateid=self.template.id,
domainid=self.domain.id,
- startvm=False
+ startvm=False,
+ mode='basic',
)
TestLinstorVolumes._start_vm(test_virtual_machine)
@@ -1020,7 +1088,7 @@ class TestLinstorVolumes(cloudstackTestCase):
test_virtual_machine.delete(self.apiClient, True)
@attr(tags=['advanced', 'migration'], required_hardware=False)
- def test_10_migrate_volume_to_distinct_instance_pool(self):
+ def test_11_migrate_volume_to_distinct_instance_pool(self):
"""Migrate volume to distinct instance pool"""
if not self.testdata[TestData.migrationTests]:
@@ -1038,7 +1106,8 @@ class TestLinstorVolumes(cloudstackTestCase):
serviceofferingid=self.compute_offering.id,
templateid=self.template.id,
domainid=self.domain.id,
- startvm=False
+ startvm=False,
+ mode='basic',
)
TestLinstorVolumes._start_vm(test_virtual_machine)
@@ -1151,6 +1220,132 @@ class TestLinstorVolumes(cloudstackTestCase):
test_virtual_machine.delete(self.apiClient, True)
+ @attr(tags=["basic"], required_hardware=False)
+ def test_12_create_vm_snapshots(self):
+ """Test to create VM snapshots
+ """
+ vm = TestLinstorVolumes._start_vm(self.virtual_machine)
+
+ try:
+ # Login to VM and write data to file system
+ self.debug("virt: {}".format(vm))
+ ssh_client = self.virtual_machine.get_ssh_client(vm.ipaddress,
retries=5)
+ ssh_client.execute("echo 'hello world' > testfile")
+ ssh_client.execute("sync")
+ except Exception as exc:
+ self.fail("SSH failed for Virtual machine {}:
{}".format(self.virtual_machine.ssh_ip, exc))
+
+ time.sleep(10)
+ memory_snapshot = False
+ vm_snapshot = VmSnapshot.create(
+ self.apiClient,
+ self.virtual_machine.id,
+ memory_snapshot,
+ "VMSnapshot1",
+ "test snapshot"
+ )
+ self.assertEqual(
+ vm_snapshot.state,
+ "Ready",
+ "Check the snapshot of vm is ready!"
+ )
+
+ @attr(tags=["basic"], required_hardware=False)
+ def test_13_revert_vm_snapshots(self):
+ """Test to revert VM snapshots
+ """
+
+ result = None
+ try:
+ ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
+ result = ssh_client.execute("rm -rf testfile")
+ except Exception as exc:
+ self.fail("SSH failed for Virtual machine %s:
%s".format(self.virtual_machine.ipaddress, exc))
+
+ if result is not None and "No such file or directory" in str(result):
+ self.fail("testfile not deleted")
+
+ time.sleep(5)
+
+ list_snapshot_response = VmSnapshot.list(
+ self.apiClient,
+ virtualmachineid=self.virtual_machine.id,
+ listall=True)
+
+ self.assertEqual(
+ isinstance(list_snapshot_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ self.assertNotEqual(
+ list_snapshot_response,
+ None,
+ "Check if snapshot exists in ListSnapshot"
+ )
+
+ self.assertEqual(
+ list_snapshot_response[0].state,
+ "Ready",
+ "Check the snapshot of vm is ready!"
+ )
+
+ self.virtual_machine.stop(self.apiClient, forced=True)
+
+ VmSnapshot.revertToSnapshot(
+ self.apiClient,
+ list_snapshot_response[0].id
+ )
+
+ TestLinstorVolumes._start_vm(self.virtual_machine)
+
+ try:
+ ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
+
+ result = ssh_client.execute("cat testfile")
+
+ except Exception as exc:
+ self.fail("SSH failed for Virtual machine {}:
{}".format(self.virtual_machine.ipaddress, exc))
+
+ self.assertEqual(
+ "hello world",
+ result[0],
+ "Check the content is the same as originaly written"
+ )
+
+ @attr(tags=["basic"], required_hardware=False)
+ def test_14_delete_vm_snapshots(self):
+ """Test to delete vm snapshots
+ """
+
+ list_snapshot_response = VmSnapshot.list(
+ self.apiClient,
+ virtualmachineid=self.virtual_machine.id,
+ listall=True)
+
+ self.assertEqual(
+ isinstance(list_snapshot_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ self.assertNotEqual(
+ list_snapshot_response,
+ None,
+ "Check if snapshot exists in ListSnapshot"
+ )
+ VmSnapshot.deleteVMSnapshot(
+ self.apiClient,
+ list_snapshot_response[0].id)
+
+ time.sleep(5)
+
+ list_snapshot_response = VmSnapshot.list(
+ self.apiClient,
+ virtualmachineid=self.virtual_machine.id,
+ listall=False)
+ self.debug('list_snapshot_response --------------------
{}'.format(list_snapshot_response))
+
+ self.assertIsNone(list_snapshot_response, "snapshot is already
deleted")
+
def _create_vm_using_template_and_destroy_vm(self, template):
vm_name = "VM-%d" % random.randint(0, 100)
@@ -1177,42 +1372,31 @@ class TestLinstorVolumes(cloudstackTestCase):
virtual_machine.delete(self.apiClient, True)
- @staticmethod
- def _get_bytes_from_gb(number_in_gb):
- return number_in_gb * 1024 * 1024 * 1024
-
def _get_volume(self, volume_id):
list_vols_response = list_volumes(self.apiClient, id=volume_id)
return list_vols_response[0]
- def _get_vm(self, vm_id):
- list_vms_response = list_virtual_machines(self.apiClient, id=vm_id)
+ @classmethod
+ def _get_vm(cls, vm_id):
+ list_vms_response = list_virtual_machines(cls.apiClient, id=vm_id)
return list_vms_response[0]
- def _get_template_cache_name(self):
- if TestData.hypervisor_type == TestData.kvm:
- return TestData.templateCacheNameKvm
-
- self.assert_(False, "Invalid hypervisor type")
-
@classmethod
def _start_vm(cls, vm):
- vm_for_check = list_virtual_machines(
- cls.apiClient,
- id=vm.id
- )[0]
+ vm_for_check = cls._get_vm(vm.id)
if vm_for_check.state == VirtualMachine.STOPPED:
vm.start(cls.apiClient)
- # For KVM, just give it 90 seconds to boot up.
- if TestData.hypervisor_type == TestData.kvm:
- time.sleep(90)
+ vm_for_check = cls._get_vm(vm.id)
+ ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)
+ return vm_for_check
@classmethod
def _reboot_vm(cls, vm):
+ vm_for_check = cls._get_vm(vm.id)
vm.reboot(cls.apiClient)
- # For KVM, just give it 90 seconds to boot up.
- if TestData.hypervisor_type == TestData.kvm:
- time.sleep(90)
+ time.sleep(5)
+
+ ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)