From e3de3707233212748566fb5283845ba004713ca6 Mon Sep 17 00:00:00 2001 From: Yang Chiu Date: Thu, 11 Apr 2024 16:57:59 +0800 Subject: [PATCH] test(robot): migrate test_snapshot test case Signed-off-by: Yang Chiu --- e2e/keywords/snapshot.resource | 42 +++++++++ e2e/keywords/volume.resource | 12 +++ e2e/libs/keywords/snapshot_keywords.py | 35 +++++++ e2e/libs/keywords/volume_keywords.py | 21 +++-- e2e/libs/snapshot/__init__.py | 1 + e2e/libs/snapshot/base.py | 76 +++++++++++++++ e2e/libs/snapshot/crd.py | 7 ++ e2e/libs/snapshot/rest.py | 120 ++++++++++++++++++++++++ e2e/libs/snapshot/snapshot.py | 52 ++++++++++ e2e/libs/utility/utility.py | 46 ++++++++- e2e/libs/volume/__init__.py | 4 +- e2e/libs/volume/base.py | 31 +++++- e2e/libs/volume/crd.py | 18 ++-- e2e/libs/volume/rest.py | 6 +- e2e/libs/volume/volume.py | 12 +-- e2e/tests/regression/test_basic.robot | 95 +++++++++++++++++++ e2e/tests/regression/volume_basic.robot | 48 ---------- 17 files changed, 544 insertions(+), 82 deletions(-) create mode 100644 e2e/keywords/snapshot.resource create mode 100644 e2e/libs/keywords/snapshot_keywords.py create mode 100644 e2e/libs/snapshot/__init__.py create mode 100644 e2e/libs/snapshot/base.py create mode 100644 e2e/libs/snapshot/crd.py create mode 100644 e2e/libs/snapshot/rest.py create mode 100644 e2e/libs/snapshot/snapshot.py create mode 100644 e2e/tests/regression/test_basic.robot delete mode 100644 e2e/tests/regression/volume_basic.robot diff --git a/e2e/keywords/snapshot.resource b/e2e/keywords/snapshot.resource new file mode 100644 index 0000000000..9e7185ef70 --- /dev/null +++ b/e2e/keywords/snapshot.resource @@ -0,0 +1,42 @@ +*** Settings *** +Documentation Snapshot Keywords + +Library ../libs/keywords/common_keywords.py +Library ../libs/keywords/snapshot_keywords.py + +*** Keywords *** +Create snapshot ${snapshot_id} of volume ${volume_id} + ${volume_name} = generate_name_with_suffix volume ${volume_id} + create_snapshot ${volume_name} ${snapshot_id} + +Delete snapshot ${snapshot_id} of volume ${volume_id} + ${volume_name} = generate_name_with_suffix volume ${volume_id} + delete_snapshot ${volume_name} ${snapshot_id} + +Revert volume ${volume_id} to snapshot ${snapshot_id} + ${volume_name} = generate_name_with_suffix volume ${volume_id} + revert_snapshot ${volume_name} ${snapshot_id} + +Purge volume ${volume_id} snapshot + ${volume_name} = generate_name_with_suffix volume ${volume_id} + purge_snapshot ${volume_name} + +Validate snapshot ${parent_id} is parent of snapshot ${child_id} in volume ${volume_id} snapshot list + ${volume_name} = generate_name_with_suffix volume ${volume_id} + is_parent_of ${volume_name} ${parent_id} ${child_id} + +Validate snapshot ${parent_id} is parent of volume-head in volume ${volume_id} snapshot list + ${volume_name} = generate_name_with_suffix volume ${volume_id} + is_parent_of_volume_head ${volume_name} ${parent_id} + +Validate snapshot ${snapshot_id} is marked as removed in volume ${volume_id} snapshot list + ${volume_name} = generate_name_with_suffix volume ${volume_id} + is_marked_as_removed ${volume_name} ${snapshot_id} + +Validate snapshot ${snapshot_id} is not in volume ${volume_id} snapshot list + ${volume_name} = generate_name_with_suffix volume ${volume_id} + is_not_existing ${volume_name} ${snapshot_id} + +Validate snapshot ${snapshot_id} is in volume ${volume_id} snapshot list + ${volume_name} = generate_name_with_suffix volume ${volume_id} + is_existing ${volume_name} ${snapshot_id} diff --git a/e2e/keywords/volume.resource b/e2e/keywords/volume.resource index c2e458e447..ec6e823a4a 100644 --- a/e2e/keywords/volume.resource +++ b/e2e/keywords/volume.resource @@ -33,6 +33,10 @@ Attach volume ${volume_id} ${volume_name} = generate_name_with_suffix volume ${volume_id} attach_volume ${volume_name} +And Attach volume ${volume_id} in maintenance mode + ${volume_name} = generate_name_with_suffix volume ${volume_id} + attach_volume_in_maintenance_mode ${volume_name} + Detach volume ${volume_id} ${volume_name} = generate_name_with_suffix volume ${volume_id} detach_volume ${volume_name} @@ -45,6 +49,10 @@ Write data to volume ${volume_id} ${volume_name} = generate_name_with_suffix volume ${volume_id} write_volume_random_data ${volume_name} 2048 +Write data ${data_id} to volume ${volume_id} + ${volume_name} = generate_name_with_suffix volume ${volume_id} + write_volume_random_data ${volume_name} 2048 ${data_id} + Keep writing data to volume ${volume_id} ${volume_name} = generate_name_with_suffix volume ${volume_id} keep_writing_data ${volume_name} @@ -73,6 +81,10 @@ Check volume ${volume_id} data is intact ${volume_name} = generate_name_with_suffix volume ${volume_id} check_data_checksum ${volume_name} +Check volume ${volume_id} data is data ${data_id} + ${volume_name} = generate_name_with_suffix volume ${volume_id} + check_data_checksum ${volume_name} ${data_id} + Check volume ${volume_id} works ${volume_name} = generate_name_with_suffix volume ${volume_id} ${volume_data_checksum} = write_volume_random_data ${volume_name} 1024 diff --git a/e2e/libs/keywords/snapshot_keywords.py b/e2e/libs/keywords/snapshot_keywords.py new file mode 100644 index 0000000000..a07ddf4844 --- /dev/null +++ b/e2e/libs/keywords/snapshot_keywords.py @@ -0,0 +1,35 @@ +from snapshot import Snapshot +from utility.utility import logging + + +class snapshot_keywords: + + def __init__(self): + self.snapshot = Snapshot() + + def create_snapshot(self, volume_name, snapshot_id): + self.snapshot.create(volume_name, snapshot_id) + + def delete_snapshot(self, volume_name, snapshot_id): + self.snapshot.delete(volume_name, snapshot_id) + + def revert_snapshot(self, volume_name, snapshot_id): + self.snapshot.revert(volume_name, snapshot_id) + + def purge_snapshot(self, volume_name): + self.snapshot.purge(volume_name) + + def is_parent_of(self, volume_name, parent_id, child_id): + self.snapshot.is_parent_of(volume_name, parent_id, child_id) + + def is_parent_of_volume_head(self, volume_name, parent_id): + self.snapshot.is_parent_of_volume_head(volume_name, parent_id) + + def is_marked_as_removed(self, volume_name, snapshot_id): + self.snapshot.is_marked_as_removed(volume_name, snapshot_id) + + def is_not_existing(self, volume_name, snapshot_id): + self.snapshot.is_not_existing(volume_name, snapshot_id) + + def is_existing(self, volume_name, snapshot_id): + self.snapshot.is_existing(volume_name, snapshot_id) diff --git a/e2e/libs/keywords/volume_keywords.py b/e2e/libs/keywords/volume_keywords.py index e8457950ab..8381af4336 100644 --- a/e2e/libs/keywords/volume_keywords.py +++ b/e2e/libs/keywords/volume_keywords.py @@ -33,7 +33,12 @@ def delete_volume(self, volume_name): def attach_volume(self, volume_name): attach_node = self.node.get_test_pod_not_running_node() logging(f'Attaching volume {volume_name} to {attach_node}') - self.volume.attach(volume_name, attach_node) + self.volume.attach(volume_name, attach_node, disable_frontend=False) + + def attach_volume_in_maintenance_mode(self, volume_name): + attach_node = self.node.get_test_pod_not_running_node() + logging(f'Attaching volume {volume_name} to {attach_node} in maintenance mode') + self.volume.attach(volume_name, attach_node, disable_frontend=True) def detach_volume(self, volume_name): logging(f'Detaching volume {volume_name}') @@ -84,21 +89,17 @@ def get_node_ids_by_replica_locality(self, volume_name, replica_locality): raise Exception(f"Failed to get node ID of the replica on {replica_locality}") - def write_volume_random_data(self, volume_name, size_in_mb): + def write_volume_random_data(self, volume_name, size_in_mb, data_id=0): logging(f'Writing {size_in_mb} MB random data to volume {volume_name}') - checksum = self.volume.write_random_data(volume_name, size_in_mb) - - self.volume.set_annotation(volume_name, ANNOT_CHECKSUM, checksum) + checksum = self.volume.write_random_data(volume_name, size_in_mb, data_id) def keep_writing_data(self, volume_name): logging(f'Keep writing data to volume {volume_name}') self.volume.keep_writing_data(volume_name) - def check_data_checksum(self, volume_name): - checksum = self.volume.get_annotation_value(volume_name, ANNOT_CHECKSUM) - - logging(f"Checking volume {volume_name} data checksum is {checksum}") - self.volume.check_data_checksum(volume_name, checksum) + def check_data_checksum(self, volume_name, data_id=0): + logging(f"Checking volume {volume_name} data {data_id} checksum") + self.volume.check_data_checksum(volume_name, data_id) def delete_replica(self, volume_name, replica_node): if str(replica_node).isdigit(): diff --git a/e2e/libs/snapshot/__init__.py b/e2e/libs/snapshot/__init__.py new file mode 100644 index 0000000000..2c59f625f0 --- /dev/null +++ b/e2e/libs/snapshot/__init__.py @@ -0,0 +1 @@ +from snapshot.snapshot import Snapshot diff --git a/e2e/libs/snapshot/base.py b/e2e/libs/snapshot/base.py new file mode 100644 index 0000000000..983831ee60 --- /dev/null +++ b/e2e/libs/snapshot/base.py @@ -0,0 +1,76 @@ +from abc import ABC, abstractmethod +from utility.utility import set_annotation +from utility.utility import get_annotation_value + +class Base(ABC): + + ANNOT_ID = "test.longhorn.io/snapshot-id" + + @abstractmethod + def create(self, volume_name, snapshot_id): + return NotImplemented + + def set_snapshot_id(self, snapshot_name, snapshot_id): + set_annotation( + group="longhorn.io", + version="v1beta2", + namespace="longhorn-system", + plural="snapshots", + name=snapshot_name, + annotation_key=self.ANNOT_ID, + annotation_value=snapshot_id + ) + + def get_snapshot_id(self, snapshot_name): + return get_annotation_value( + group="longhorn.io", + version="v1beta2", + namespace="longhorn-system", + plural="snapshots", + name=snapshot_name, + annotation_key=self.ANNOT_ID + ) + + @abstractmethod + def get(self, volume_name, snapshot_id): + return NotImplemented + + @abstractmethod + def get_volume_head(self, volume_name): + return NotImplemented + + @abstractmethod + def list(self, volume_name): + return NotImplemented + + @abstractmethod + def delete(self, volume_name, snapshot_id): + return NotImplemented + + @abstractmethod + def revert(self, volume_name, snapshot_id): + return NotImplemented + + @abstractmethod + def purge(self, volume_name): + return NotImplemented + + @abstractmethod + def is_parent_of(self, volume_name, parent_id, child_id): + return NotImplemented + + @abstractmethod + def is_parent_of_volume_head(self, volume_name, parent_id): + return NotImplemented + + @abstractmethod + def is_existing(self, volume_name, snapshot_id): + return NotImplemented + + @abstractmethod + def is_not_existing(self, volume_name, snapshot_id): + return NotImplemented + + @abstractmethod + def is_marked_as_removed(self, volume_name, snapshot_id): + return NotImplemented diff --git a/e2e/libs/snapshot/crd.py b/e2e/libs/snapshot/crd.py new file mode 100644 index 0000000000..b4f09f0e0e --- /dev/null +++ b/e2e/libs/snapshot/crd.py @@ -0,0 +1,7 @@ +from snapshot.base import Base + + +class CRD(Base): + + def __init__(self): + pass diff --git a/e2e/libs/snapshot/rest.py b/e2e/libs/snapshot/rest.py new file mode 100644 index 0000000000..eefad2f0a7 --- /dev/null +++ b/e2e/libs/snapshot/rest.py @@ -0,0 +1,120 @@ +from snapshot.base import Base +from utility.utility import logging +from utility.utility import get_longhorn_client +from utility.utility import get_retry_count_and_interval +from node_exec import NodeExec +from volume import Rest as RestVolume +import time + + +class Rest(Base): + + def __init__(self): + self.volume = RestVolume(NodeExec.get_instance()) + self.retry_count, self.retry_interval = get_retry_count_and_interval() + + def create(self, volume_name, snapshot_id): + logging(f"Creating volume {volume_name} snapshot {snapshot_id}") + volume = self.volume.get(volume_name) + snapshot = volume.snapshotCreate() + snap_name = snapshot.name + + snapshot_created = False + for i in range(self.retry_count): + snapshots = volume.snapshotList().data + for vs in snapshots: + if vs.name == snap_name: + snapshot_created = True + break + if snapshot_created is True: + break + time.sleep(self.retry_interval) + + assert snapshot_created + + self.set_snapshot_id(snap_name, snapshot_id) + + return snapshot + + def get(self, volume_name, snapshot_id): + snapshots = self.list(volume_name) + for snapshot in snapshots: + if snapshot.name != "volume-head" and self.get_snapshot_id(snapshot.name) == snapshot_id: + return snapshot + return None + + def get_volume_head(self, volume_name): + snapshots = self.list(volume_name) + for snapshot in snapshots: + if snapshot.name == "volume-head": + return snapshot + assert False + + def list(self, volume_name): + return self.volume.get(volume_name).snapshotList().data + + def delete(self, volume_name, snapshot_id): + logging(f"Deleting volume {volume_name} snapshot {snapshot_id}") + snapshot = self.get(volume_name, snapshot_id) + self.volume.get(volume_name).snapshotDelete(name=snapshot.name) + + def revert(self, volume_name, snapshot_id): + logging(f"Reverting volume {volume_name} to snapshot {snapshot_id}") + snapshot = self.get(volume_name, snapshot_id) + self.volume.get(volume_name).snapshotRevert(name=snapshot.name) + + def purge(self, volume_name): + logging(f"Purging volume {volume_name} snapshot") + + volume = self.volume.get(volume_name) + volume.snapshotPurge() + + completed = 0 + last_purge_progress = {} + purge_status = {} + for i in range(self.retry_count): + completed = 0 + volume = self.volume.get(volume_name) + purge_status = volume.purgeStatus + for status in purge_status: + assert status.error == "", f"Expect purge without error, but its' {status.error}" + + progress = status.progress + assert progress <= 100, f"Expect purge progress <= 100, but it's {status}" + replica = status.replica + last = last_purge_progress.get(replica) + assert last is None or last <= status.progress, f"Expect purge progress increasing, but it didn't: current status = {status}, last progress = {last_purge_progress}" + last_purge_progress["replica"] = progress + + if status.state == "complete": + assert progress == 100 + completed += 1 + if completed == len(purge_status): + break + time.sleep(self.retry_interval) + assert completed == len(purge_status) + + def is_parent_of(self, volume_name, parent_id, child_id): + logging(f"Checking volume {volume_name} snapshot {parent_id} is parent of snapshot {child_id}") + parent = self.get(volume_name, parent_id) + child = self.get(volume_name, child_id) + if child.name not in parent.children.keys() or child.parent != parent.name: + logging(f"Expect snapshot {parent_id} is parent of snapshot {child_id}, but it's not") + time.sleep(self.retry_count) + + def is_parent_of_volume_head(self, volume_name, parent_id): + parent = self.get(volume_name, parent_id) + volume_head = self.get_volume_head(volume_name) + if volume_head.name not in parent.children.keys() or volume_head.parent != parent.name: + logging(f"Expect snapshot {parent_id} is parent of volume-head, but it's not") + time.sleep(self.retry_count) + + def is_existing(self, volume_name, snapshot_id): + assert self.get(volume_name, snapshot_id) + + def is_not_existing(self, volume_name, snapshot_id): + assert not self.get(volume_name, snapshot_id) + + def is_marked_as_removed(self, volume_name, snapshot_id): + snapshot = self.get(volume_name, snapshot_id) + assert snapshot.removed diff --git a/e2e/libs/snapshot/snapshot.py b/e2e/libs/snapshot/snapshot.py new file mode 100644 index 0000000000..0f39a9cd32 --- /dev/null +++ b/e2e/libs/snapshot/snapshot.py @@ -0,0 +1,52 @@ +from snapshot.base import Base +from snapshot.crd import CRD +from snapshot.rest import Rest +from strategy import LonghornOperationStrategy +from utility.utility import logging + + +class Snapshot(Base): + + _strategy = LonghornOperationStrategy.REST + + def __init__(self): + if self._strategy == LonghornOperationStrategy.CRD: + self.snapshot = CRD() + else: + self.snapshot = Rest() + + def create(self, volume_name, snapshot_id): + return self.snapshot.create(volume_name, snapshot_id) + + def get(self, volume_name, snapshot_id): + return self.snapshot.get(volume_name, snapshot_id) + + def get_volume_head(self, volume_name): + return self.snapshot.get_volume_head(volume_name) + + def list(self, volume_name): + return self.snapshot.list(volume_name) + + def delete(self, volume_name, snapshot_id): + return self.snapshot.delete(volume_name, snapshot_id) + + def revert(self, volume_name, snapshot_id): + return self.snapshot.revert(volume_name, snapshot_id) + + def purge(self, volume_name): + return self.snapshot.purge(volume_name) + + def is_parent_of(self, volume_name, parent_id, child_id): + return self.snapshot.is_parent_of(volume_name, parent_id, child_id) + + def is_parent_of_volume_head(self, volume_name, parent_id): + return self.snapshot.is_parent_of_volume_head(volume_name, parent_id) + + def is_existing(self, volume_name, snapshot_id): + return self.snapshot.is_existing(volume_name, snapshot_id) + + def is_not_existing(self, volume_name, snapshot_id): + return self.snapshot.is_not_existing(volume_name, snapshot_id) + + def is_marked_as_removed(self, volume_name, snapshot_id): + return self.snapshot.is_marked_as_removed(volume_name, snapshot_id) diff --git a/e2e/libs/utility/utility.py b/e2e/libs/utility/utility.py index 4a9746c7a5..0938662c4b 100644 --- a/e2e/libs/utility/utility.py +++ b/e2e/libs/utility/utility.py @@ -100,11 +100,14 @@ def apply_cr_from_yaml(filepath): def get_cr(group, version, namespace, plural, name): api = client.CustomObjectsApi() - try: - resp = api.get_namespaced_custom_object(group, version, namespace, plural, name) - return resp - except ApiException as e: - logging(f"Getting namespaced custom object error: {e}") + retry_count, retry_interval = get_retry_count_and_interval() + for i in range(retry_count): + try: + resp = api.get_namespaced_custom_object(group, version, namespace, plural, name) + return resp + except ApiException as e: + logging(f"Getting namespaced custom object error: {e}") + time.sleep(retry_interval) def filter_cr(group, version, namespace, plural, field_selector="", label_selector=""): @@ -116,6 +119,39 @@ def filter_cr(group, version, namespace, plural, field_selector="", label_select logging(f"Listing namespaced custom object: {e}") +def set_annotation(group, version, namespace, plural, name, annotation_key, annotation_value): + api = client.CustomObjectsApi() + # retry conflict error + retry_count, retry_interval = get_retry_count_and_interval() + for i in range(retry_count): + logging(f"Try to set custom resource {plural} {name} annotation {annotation_key}={annotation_value} ... ({i})") + try: + cr = get_cr(group, version, namespace, plural, name) + annotations = cr['metadata'].get('annotations', {}) + annotations[annotation_key] = annotation_value + cr['metadata']['annotations'] = annotations + api.replace_namespaced_custom_object( + group=group, + version=version, + namespace=namespace, + plural=plural, + name=name, + body=cr + ) + break + except Exception as e: + if e.status == 409: + logging(f"Conflict error: {e.body}, retry ({i}) ...") + else: + raise e + time.sleep(retry_interval) + + +def get_annotation_value(group, version, namespace, plural, name, annotation_key): + cr = get_cr(group, version, namespace, plural, name) + return cr['metadata']['annotations'].get(annotation_key) + + def wait_delete_ns(name): api = client.CoreV1Api() retry_count, retry_interval = get_retry_count_and_interval() diff --git a/e2e/libs/volume/__init__.py b/e2e/libs/volume/__init__.py index a56b424ae0..d087f25ebb 100644 --- a/e2e/libs/volume/__init__.py +++ b/e2e/libs/volume/__init__.py @@ -1 +1,3 @@ -from volume.volume import Volume \ No newline at end of file +from volume.volume import Volume +from volume.rest import Rest +from volume.crd import CRD diff --git a/e2e/libs/volume/base.py b/e2e/libs/volume/base.py index 487f877a56..8d954f46a0 100644 --- a/e2e/libs/volume/base.py +++ b/e2e/libs/volume/base.py @@ -1,8 +1,12 @@ from abc import ABC, abstractmethod +from utility.utility import set_annotation +from utility.utility import get_annotation_value class Base(ABC): + ANNOT_DATA_CHECKSUM = f'test.longhorn.io/data-checksum-' + @abstractmethod def get(self, volume_name): return NotImplemented @@ -11,8 +15,29 @@ def get(self, volume_name): def create(self, volume_name, size, replica_count): return NotImplemented + def set_data_checksum(self, volume_name, data_id, checksum): + set_annotation( + group="longhorn.io", + version="v1beta2", + namespace="longhorn-system", + plural="volumes", + name=volume_name, + annotation_key=f"{self.ANNOT_DATA_CHECKSUM}{data_id}", + annotation_value=checksum + ) + + def get_data_checksum(self, volume_name, data_id): + return get_annotation_value( + group="longhorn.io", + version="v1beta2", + namespace="longhorn-system", + plural="volumes", + name=volume_name, + annotation_key=f"{self.ANNOT_DATA_CHECKSUM}{data_id}", + ) + @abstractmethod - def attach(self, volume_name, node_name): + def attach(self, volume_name, node_name, disable_frontend): return NotImplemented @abstractmethod @@ -28,7 +53,7 @@ def get_endpoint(self, volume_name): return NotImplemented @abstractmethod - def write_random_data(self, volume_name, size): + def write_random_data(self, volume_name, size, data_id): return NotImplemented @abstractmethod @@ -44,7 +69,7 @@ def wait_for_replica_rebuilding_complete(self, volume_name, node_name): return NotImplemented @abstractmethod - def check_data_checksum(self, volume_name, checksum): + def check_data_checksum(self, volume_name, data_id): return NotImplemented # @abstractmethod diff --git a/e2e/libs/volume/crd.py b/e2e/libs/volume/crd.py index 6abea3dfe8..b88f19b0cf 100644 --- a/e2e/libs/volume/crd.py +++ b/e2e/libs/volume/crd.py @@ -71,7 +71,7 @@ def delete(self, volume_name): except Exception as e: logging(f"Deleting volume error: {e}") - def attach(self, volume_name, node_name): + def attach(self, volume_name, node_name, disable_frontend): self.obj_api.patch_namespaced_custom_object( group="longhorn.io", version="v1beta2", @@ -100,7 +100,7 @@ def attach(self, volume_name, node_name): "id": "", "nodeID": node_name, "parameters": { - "disableFrontend": "false", + "disableFrontend": "true" if disable_frontend else "false", "lastAttachedBy": "" }, "type": "longhorn-api" @@ -115,6 +115,8 @@ def attach(self, volume_name, node_name): if e.reason != "Not Found": Exception(f'exception for creating volumeattachments:', e) self.wait_for_volume_state(volume_name, "attached") + volume = self.get(volume_name) + assert volume['status']['frontendDisabled'] == disable_frontend, f"expect volume frontendDisabled is {disable_frontend}, but it's {volume['status']['frontendDisabled']}" def detach(self, volume_name): try: @@ -277,7 +279,7 @@ def get_endpoint(self, volume_name): logging("Delegating the get_endpoint call to API because there is no CRD implementation") return Rest(self.node_exec).get_endpoint(volume_name) - def write_random_data(self, volume_name, size): + def write_random_data(self, volume_name, size, data_id): node_name = self.get(volume_name)["spec"]["nodeID"] endpoint = self.get_endpoint(volume_name) @@ -298,7 +300,9 @@ def write_random_data(self, volume_name, size): f"Failed to write random data to volume {volume_name}.\n" \ f"Command: {dd_command}\n" \ f"Output: {dd_command_output}" - return self.node_exec.issue_cmd(node_name, f"md5sum {endpoint} | awk \'{{print $1}}\'") + checksum = self.node_exec.issue_cmd(node_name, f"md5sum {endpoint} | awk \'{{print $1}}\'") + logging(f"Storing volume {volume_name} data {data_id} checksum = {checksum}") + self.set_data_checksum(volume_name, data_id, checksum) def keep_writing_data(self, volume_name, size): node_name = self.get(volume_name)["spec"]["nodeID"] @@ -338,13 +342,15 @@ def wait_for_replica_rebuilding_complete(self, volume_name, node_name): node_name ) - def check_data_checksum(self, volume_name, checksum): + def check_data_checksum(self, volume_name, data_id): + expected_checksum = self.get_data_checksum(volume_name, data_id) node_name = self.get(volume_name)["spec"]["nodeID"] endpoint = self.get_endpoint(volume_name) actual_checksum = self.node_exec.issue_cmd( node_name, f"md5sum {endpoint} | awk \'{{print $1}}\'") - assert actual_checksum == checksum + logging(f"Checked volume {volume_name} data {data_id}. Expected checksum = {expected_checksum}. Actual checksum = {actual_checksum}") + assert actual_checksum == expected_checksum def validate_volume_replicas_anti_affinity(self, volume_name): replica_list = self.obj_api.list_namespaced_custom_object( diff --git a/e2e/libs/volume/rest.py b/e2e/libs/volume/rest.py index 4679edc41e..22a25ced7e 100644 --- a/e2e/libs/volume/rest.py +++ b/e2e/libs/volume/rest.py @@ -29,7 +29,7 @@ def get(self, volume_name): def create(self, volume_name, size, replica_count): return NotImplemented - def attach(self, volume_name, node_name): + def attach(self, volume_name, node_name, disable_frontend): return NotImplemented def delete(self, volume_name): @@ -67,7 +67,7 @@ def get_endpoint(self, volume_name): assert endpoint.startswith("iscsi://") return endpoint - def write_random_data(self, volume_name, size): + def write_random_data(self, volume_name, size, data_id): return NotImplemented def keep_writing_data(self, volume_name, size): @@ -140,7 +140,7 @@ def wait_for_replica_rebuilding_complete(self, volume_name, node_name): time.sleep(RETRY_INTERVAL) assert completed - def check_data_checksum(self, volume_name, checksum): + def check_data_checksum(self, volume_name, data_id): return NotImplemented def cleanup(self, volume_names): diff --git a/e2e/libs/volume/volume.py b/e2e/libs/volume/volume.py index 5d9cdb7bb9..4c83a33862 100644 --- a/e2e/libs/volume/volume.py +++ b/e2e/libs/volume/volume.py @@ -24,8 +24,8 @@ def create(self, volume_name, size, replica_count, frontend="blockdev"): def delete(self, volume_name): return self.volume.delete(volume_name) - def attach(self, volume_name, node_name): - return self.volume.attach(volume_name, node_name) + def attach(self, volume_name, node_name, disable_frontend): + return self.volume.attach(volume_name, node_name, disable_frontend) def detach(self, volume_name): return self.volume.detach(volume_name) @@ -65,8 +65,8 @@ def wait_for_volume_expand_to_size(self, volume_name, size): def get_endpoint(self, volume_name): return self.volume.get_endpoint(volume_name) - def write_random_data(self, volume_name, size): - return self.volume.write_random_data(volume_name, size) + def write_random_data(self, volume_name, size, data_id): + return self.volume.write_random_data(volume_name, size, data_id) def keep_writing_data(self, volume_name): return self.volume.keep_writing_data(volume_name, 256) @@ -86,8 +86,8 @@ def wait_for_replica_rebuilding_complete(self, volume_name, node_name): node_name ) - def check_data_checksum(self, volume_name, checksum): - return self.volume.check_data_checksum(volume_name, checksum) + def check_data_checksum(self, volume_name, data_id): + return self.volume.check_data_checksum(volume_name, data_id) def validate_volume_replicas_anti_affinity(self, volume_name): return self.volume.validate_volume_replicas_anti_affinity(volume_name) diff --git a/e2e/tests/regression/test_basic.robot b/e2e/tests/regression/test_basic.robot new file mode 100644 index 0000000000..ac632b53f6 --- /dev/null +++ b/e2e/tests/regression/test_basic.robot @@ -0,0 +1,95 @@ +*** Settings *** +Documentation Basic Test Cases + +Test Tags regression + +Resource ../keywords/common.resource +Resource ../keywords/deployment.resource +Resource ../keywords/persistentvolumeclaim.resource +Resource ../keywords/recurringjob.resource +Resource ../keywords/statefulset.resource +Resource ../keywords/volume.resource +Resource ../keywords/snapshot.resource + +Test Setup Set test environment +Test Teardown Cleanup test resources + +*** Variables *** +${LOOP_COUNT} 1 +${RETRY_COUNT} 300 +${RETRY_INTERVAL} 1 + +*** Test Cases *** +Volume Basic Test + [Tags] coretest + [Documentation] Test basic volume operations + ... + ... 1. Check volume name and parameter + ... 2. Create a volume and attach to the current node, then check volume states + ... 3. Check soft anti-affinity rule + ... 4. Write then read back to check volume data + Given Create volume wrong_volume-name-1.0 + Then No volume created + + Given Create volume wrong_volume-name + Then No volume created + + Given Create volume 0 with frontend invalid_frontend + Then No volume created + + Given Create volume 0 with 2 GB and 3 replicas + When Attach volume 0 + And Wait for volume 0 healthy + Then Validate volume 0 replicas anti-affinity + + When Write data to volume 0 + Then Check volume 0 data is intact + + And Detach volume 0 + And Delete volume 0 + +Test Snapshot + [Tags] coretest + [Documentation] Test snapshot operations + Given Create volume 0 + When Attach volume 0 + And Wait for volume 0 healthy + + And Create snapshot 0 of volume 0 + + And Write data 1 to volume 0 + And Create snapshot 1 of volume 0 + + And Write data 2 to volume 0 + And Create snapshot 2 of volume 0 + + Then Validate snapshot 0 is parent of snapshot 1 in volume 0 snapshot list + And Validate snapshot 1 is parent of snapshot 2 in volume 0 snapshot list + And Validate snapshot 2 is parent of volume-head in volume 0 snapshot list + + When Delete snapshot 2 of volume 0 + Then Validate snapshot 2 is in volume 0 snapshot list + And Validate snapshot 2 is marked as removed in volume 0 snapshot list + And Check volume 0 data is data 2 + + When Detach volume 0 + And Attach volume 0 in maintenance mode + And Wait for volume 0 healthy + + And Revert volume 0 to snapshot 1 + And Detach volume 0 + And Attach volume 0 + And Wait for volume 0 healthy + Then Check volume 0 data is data 1 + And Validate snapshot 1 is parent of volume-head in volume 0 snapshot list + + When Delete snapshot 1 of volume 0 + And Delete snapshot 0 of volume 0 + + And Purge volume 0 snapshot + Then Validate snapshot 0 is not in volume 0 snapshot list + And Validate snapshot 1 is in volume 0 snapshot list + And Validate snapshot 1 is marked as removed in volume 0 snapshot list + And Validate snapshot 2 is not in volume 0 snapshot list + + And Check volume 0 data is data 1 diff --git a/e2e/tests/regression/volume_basic.robot b/e2e/tests/regression/volume_basic.robot deleted file mode 100644 index 98b03d5b5a..0000000000 --- a/e2e/tests/regression/volume_basic.robot +++ /dev/null @@ -1,48 +0,0 @@ -*** Settings *** -Documentation Basic Test Cases - -Test Tags regression - -Resource ../keywords/common.resource -Resource ../keywords/deployment.resource -Resource ../keywords/persistentvolumeclaim.resource -Resource ../keywords/recurringjob.resource -Resource ../keywords/statefulset.resource -Resource ../keywords/volume.resource - -Test Setup Set test environment -Test Teardown Cleanup test resources - -*** Variables *** -${LOOP_COUNT} 1 -${RETRY_COUNT} 300 -${RETRY_INTERVAL} 1 - -*** Test Cases *** -Volume Basic Test - [Tags] coretest - [Documentation] Test basic volume operations - ... - ... 1. Check volume name and parameter - ... 2. Create a volume and attach to the current node, then check volume states - ... 3. Check soft anti-affinity rule - ... 4. Write then read back to check volume data - When Create volume wrong_volume-name-1.0 - Then No volume created - - When Create volume wrong_volume-name - Then No volume created - - When Create volume 0 with frontend invalid_frontend - Then No volume created - - When Create volume 0 with 2 GB and 3 replicas - And Attach volume 0 - And Wait for volume 0 healthy - Then Validate volume 0 replicas anti-affinity - - And Write data to volume 0 - Then Check volume 0 data is intact - - And Detach volume 0 - And Delete volume 0