Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions harvester_robot_tests/keywords/common.resource
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,31 @@ Set up test environment
common_keywords.init harvester api client ${HARVESTER_ENDPOINT} ${HARVESTER_USERNAME} ${HARVESTER_PASSWORD}
common_keywords.init k8s api client

Generate Unique Name
[Arguments] ${prefix1}=${EMPTY} ${prefix2}=${EMPTY}
[Documentation] Generate a unique name with optional prefixes and yymmdd-HHMMSS-ffffqa timestamp suffix
... Generate Unique Name -> '260603-145834-1273qa'
... Generate Unique Name foo -> 'foo-260603-145834-1273qa'
... Generate Unique Name foo bar -> 'foo-bar-2260603-145834-1273qa'
${name}= common_keywords.generate_name_with_suffix ${prefix1} ${prefix2}
RETURN ${name}

Cleanup test resources
[Documentation] Clean up all resources created during tests
Log Cleaning up test resources
Run Keyword And Ignore Error common_keywords.Cleanup VMs
Run Keyword And Ignore Error common_keywords.Cleanup volumes
Run Keyword And Ignore Error common_keywords.Cleanup images
Run Keyword And Ignore Error common_keywords.Cleanup networks
Run Keyword And Ignore Error common_keywords.Cleanup backups
Run Keyword And Ignore Error common_keywords.cleanup_vms
Run Keyword And Ignore Error common_keywords.cleanup_volumes
Run Keyword And Ignore Error common_keywords.cleanup_images
Run Keyword And Ignore Error common_keywords.cleanup_storageclasses
Run Keyword And Ignore Error common_keywords.cleanup_networks
Run Keyword And Ignore Error common_keywords.cleanup_backups

Common Suite Teardown
Run Keyword If All Tests Passed Cleanup test resources
Run Keyword If Any Tests Failed Log Variables
Comment thread
albinsun marked this conversation as resolved.

Common Test Teardown
Run Keyword If Test Failed Log Variables

List Pods By Label
[Arguments] ${namespace} ${label_selector} ${status}=${None}
Expand Down
8 changes: 8 additions & 0 deletions harvester_robot_tests/keywords/host.resource
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,11 @@ Count Standard Nodes
${node_names}= host_keywords.get_standard_nodes
${node_count}= Get Length ${node_names}
RETURN ${node_count}

Tag Storages
[Arguments] ${tag} ${disk_by_node}
FOR ${node} ${disk} IN &{disk_by_node}
host_keywords.add_lh_node_disk_tag ${node} ${disk} ${tag}
Wait Until Keyword Succeeds ${WAIT_TIMEOUT} ${RETRY_INTERVAL}
... host_keywords.is_lh_node_disk_tag_present ${node} ${disk} ${tag}
END
12 changes: 9 additions & 3 deletions harvester_robot_tests/keywords/image.resource
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Library ../libs/keywords/image_keywords.py

*** Keywords ***
Create image from url with name
[Arguments] ${image_name} ${image_url} ${checksum}=
[Documentation] Create image from URL using provided name
image_keywords.create image from url ${image_name} ${image_url} checksum=${checksum}
[Arguments] ${image_name} ${image_url} ${storage_class}=${EMPTY} ${checksum}=
[Documentation] Create image from URL using provided name and optional storage class
image_keywords.create image from url ${image_name} ${image_url} storage_class=${storage_class} checksum=${checksum}

Wait for image downloaded by name
[Arguments] ${image_name} ${timeout}=${WAIT_TIMEOUT}
Expand All @@ -25,3 +25,9 @@ Get image status by name
[Documentation] Get image status
${status}= image_keywords.get image status ${image_name} ${namespace}
RETURN ${status}

Image is available for VM creation
[Arguments] ${image_name} ${image_url}
[Documentation] Create an image from URL (delegates to Image module).
Create image from url with name ${image_name} ${image_url}
Wait for image downloaded by name ${image_name}
42 changes: 15 additions & 27 deletions harvester_robot_tests/keywords/storage.resource
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ Resource common.resource
Resource host.resource


*** Variables ***
&{DATA_DISK_BY_NODE} &{EMPTY}
${LHv2_SC_REPLICAS} ${EMPTY}


*** Keywords ***
# instance-manager (IM)
Standard Nodes Have Running v2 IM Pods
Expand All @@ -22,38 +17,31 @@ Standard Nodes Have Running v2 IM Pods
Should Be Equal As Integers ${running_v2_im_pods_count} ${standard_nodes_count}

Standard Nodes Have No v2 IM Pods
${standard_nodes_count}= host.Count Standard Nodes
${v2_im_pods_count}= common.Count Pods By Label
... ${LONGHORN_NAMESPACE}
... longhorn.io/data-engine=v2,longhorn.io/component=instance-manager
Should Be Equal As Integers ${v2_im_pods_count} 0

# Provision LHv2 Storages
Pick Unprovisioned Data Disks
[Arguments] ${namespace}=${LONGHORN_NAMESPACE}
[Documentation] Pick usable data disks per standard node as disk name by node name
&{disk_by_node}= storage_keywords.pick_unprovisioned_data_disks ${namespace}
RETURN &{disk_by_node}

Cluster Has Available Data Disks And Set Suite Variables
Get Available NVMe Disks
[Documentation] Check if there are 1 to 3 unprovisioned data disks
&{DATA_DISK_BY_NODE}= Pick Unprovisioned Data Disks
&{DATA_DISK_BY_NODE}= Evaluate dict(list($DATA_DISK_BY_NODE.items())[:3])
${data_disks_count}= Get Length ${DATA_DISK_BY_NODE}
Should Be True ${data_disks_count} >= 1 Cluster has NO available data disk
&{node_nvme_map}= storage_keywords.pick_unprovisioned_nvme_disks ${LONGHORN_NAMESPACE}
&{node_nvme_map}= Evaluate dict(list($node_nvme_map.items())[:3])
${nvme_count}= Get Length ${node_nvme_map}
Should Be True ${nvme_count} >= 1 Cluster has NO available data disk
RETURN &{node_nvme_map}

Set Suite Variable &{DATA_DISK_BY_NODE}
Set Suite Metadata DATA_DISK_BY_NODE ${DATA_DISK_BY_NODE}
Set Suite Variable ${LHv2_SC_REPLICAS} ${data_disks_count}
Set Suite Metadata LHv2_SC_REPLICAS ${LHv2_SC_REPLICAS}

Provision Storages With LHv2 Data Engine
[Arguments] &{disk_by_node}
Provision Storages
[Arguments] ${engine} ${disk_by_node}
FOR ${node} ${disk} IN &{disk_by_node}
Set Test Message \nProvision blockdevice ${LONGHORN_NAMESPACE}/${disk} with LHv2 append=yes
storage_keywords.provision_longhorn_storage ${disk} LonghornV2 ${LONGHORN_NAMESPACE}
storage_keywords.provision_longhorn_storage ${disk} ${engine} ${LONGHORN_NAMESPACE}
END

Provision Storages With LHv2 Data Engine
[Arguments] ${disk_by_node}
Provision Storages LonghornV2 ${disk_by_node}

Blockdevice Is Provisioned
[Arguments] ${name} ${namespace}=${LONGHORN_NAMESPACE}
${is_provisioned}= storage_keywords.is_blockdevice_provisioned ${name} ${namespace}
Expand All @@ -65,8 +53,8 @@ Longhorn Node Disk Is ${condition}
${cond_status}= storage_keywords.get_lh_node_disk_status_condition ${node_name} ${disk_name} ${condition}
Should Be True ${cond_status}

Wait Until LHv2 Storages Are Provisioned
[Arguments] &{disk_by_node}
Wait Until Storages Are Provisioned
[Arguments] ${disk_by_node}
[Documentation] Check provisioned storages are ``Ready``, ``Schedulable`` and ``Provisioned``
FOR ${node} ${disk} IN &{disk_by_node}
Log Waiting storage ${disk} on node ${node} become ``Ready``, ``Schedulable`` and ``Provisioned``...
Expand Down
27 changes: 27 additions & 0 deletions harvester_robot_tests/keywords/storageclass.resource
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
*** Settings ***
Documentation StorageClass Keywords
Library ../libs/keywords/storage_keywords.py
Resource variables.resource


*** Keywords ***
Create Storage Class
[Arguments] ${name} ${data_engine} ${number_of_replicas} ${disk_selector}
[Documentation] Create a StorageClass with Longhorn provisioner.
... disk_selector can be a comma-separated list (e.g. lhv2,fast).
storage_keywords.create_storageclass ${name} ${data_engine} ${number_of_replicas} ${disk_selector}
Wait Until Keyword Succeeds ${WAIT_TIMEOUT} ${RETRY_INTERVAL}
... Storage Class Is Present ${name}

Create LHv2 Storage Class
[Arguments] ${name} ${number_of_replicas} ${disk_selector}
Create Storage Class ${name} v2 ${number_of_replicas} ${disk_selector}

Delete Storage Class
[Arguments] ${name}
storage_keywords.delete_storageclass ${name}

Storage Class Is Present
[Arguments] ${name}
${sc}= storage_keywords.get_storageclass ${name}
Should Not Be Empty ${sc}
2 changes: 1 addition & 1 deletion harvester_robot_tests/keywords/variables.resource
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ ${HARVESTER_USERNAME} %{HARVESTER_USERNAME=admin}
${HARVESTER_PASSWORD} %{HARVESTER_PASSWORD=password}

# Timeouts and Intervals
${WAIT_TIMEOUT} 600
${WAIT_TIMEOUT} %{WAIT_TIMEOUT=600}
${RETRY_INTERVAL} 3
${SLEEP_TIMEOUT} 3

Expand Down
17 changes: 7 additions & 10 deletions harvester_robot_tests/keywords/virtualmachine.resource
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
*** Settings ***
Documentation VM Keywords
Resource variables.resource
Resource common.resource
Resource image.resource
Library ../libs/keywords/vm_keywords.py

*** Keywords ***
Expand All @@ -13,9 +11,9 @@ List VMs by prefix
RETURN ${vms}

VM is created
[Arguments] ${vm_name} &{config}
[Arguments] ${vm_name} ${image_id} ${cpu}=2 ${memory}=4Gi &{config}
[Documentation] Create a virtual machine with provided name
vm_keywords.create vm ${vm_name} &{config}
vm_keywords.create vm ${vm_name} ${image_id} ${cpu} ${memory} &{config}

VM is deleted
[Arguments] ${vm_name}
Expand Down Expand Up @@ -48,7 +46,7 @@ VM should be deleted
vm_keywords.wait for vm deleted ${vm_name}

VM should have IP addresses
[Arguments] ${vm_name} ${networks}
[Arguments] ${vm_name} ${networks}=${None}
[Documentation] Wait for VM to obtain IP addresses
vm_keywords.wait for vm ip addresses ${vm_name} ${networks}

Expand All @@ -64,8 +62,7 @@ Get VM data checksum
${checksum}= vm_keywords.get vm data checksum ${vm_name}
RETURN ${checksum}

Image is available for VM creation
[Arguments] ${image_name} ${image_url}
[Documentation] Create an image from URL (delegates to Image module).
image.Create image from url with name ${image_name} ${image_url}
image.Wait for image downloaded by name ${image_name}
Update Volume Size to ${new_size}Gi From VM
[Arguments] ${vm_name} ${disk_name}
[Documentation] Update the size of a VM disk
vm_keywords.update_vm_disk_size ${vm_name} ${disk_name} ${new_size}Gi
39 changes: 39 additions & 0 deletions harvester_robot_tests/keywords/volume.resource
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
*** Settings ***
Documentation Volume Keywords
Resource variables.resource
Library ../libs/keywords/volume_keywords.py

*** Keywords ***
# size
Get Volume Size
[Arguments] ${vol_name}
${status}= volume_keywords.get_volume_status ${vol_name}
RETURN ${status["capacity"]["storage"]}

Volume Size Is ${size_gi}Gi
[Arguments] ${vol_name}
${actual_size}= Get Volume Size ${vol_name}
Should Be Equal As Strings ${actual_size} ${size_gi}Gi

Update Volume Size to ${new_size}Gi
[Arguments] ${vol_name}
volume_keywords.expand_volume ${vol_name} ${new_size}Gi

# status
Volume Phase Is ${phase}
[Arguments] ${vol_name}
${status}= volume_keywords.get_volume_status ${vol_name}
Should Be Equal As Strings ${status["phase"]} ${phase}

Volume State Is Ready/In-use
[Arguments] ${vol_name}
${status}= volume_keywords.get_volume_status ${vol_name}
Should Be Empty ${status["conditions"]}

Wait Until Volume Is Active
[Arguments] ${vol_name}
Sleep ${SLEEP_TIMEOUT}s reason=Buffer time for volume changes
Wait Until Keyword Succeeds ${WAIT_TIMEOUT} ${RETRY_INTERVAL}
... Volume Phase Is Bound ${vol_name}
Wait Until Keyword Succeeds ${WAIT_TIMEOUT} ${RETRY_INTERVAL}
... Volume State Is Ready/In-use ${vol_name}
21 changes: 6 additions & 15 deletions harvester_robot_tests/libs/blockdevice/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,17 @@


class Base(ABC):
def __init__(self):
self.unsupported_msg = f"Unsupported by {self.__class__.__name__}, falling back."

@abstractmethod
def list(self, namespace):
"""List blockdevices, optionally by namespace

Return a list of blockdevices
"""
pass
raise NotImplementedError(self.unsupported_msg)

@abstractmethod
def get(self, name, namespace):
"""Get blockdevice, optionally by name and namespace

Return a blockdevice or None
"""
pass
raise NotImplementedError(self.unsupported_msg)

@abstractmethod
def provision_longhorn_storage(self, name, engine_version, namespace):
"""Provision a blockdevice for Longhorn storage

Return nothing, but raise exception if operation fails
"""
pass
raise NotImplementedError(self.unsupported_msg)
36 changes: 18 additions & 18 deletions harvester_robot_tests/libs/blockdevice/blockdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
Layer 4: Component and its implementation
"""

import os
from constant import HarvesterOperationStrategy
from utility.utility import logging
from .base import Base
from .crd import CRD
from .rest import Rest
Expand All @@ -13,25 +12,26 @@
class Blockdevice(Base):
def __init__(self):
"""Initialize Blockdevice component"""
try:
strategy_str = os.getenv("HARVESTER_OPERATION_STRATEGY", "crd").lower()
self._strategy = HarvesterOperationStrategy(strategy_str)
except ValueError:
self._strategy = HarvesterOperationStrategy.CRD

match self._strategy:
case HarvesterOperationStrategy.CRD:
self.blockdevice = CRD()
case HarvesterOperationStrategy.REST:
self.blockdevice = Rest()
self.crd = CRD()
self.rest = Rest()

def list(self, namespace):
blockdevices = self.blockdevice.list(namespace)
return blockdevices
try:
return self.crd.list(namespace)
except NotImplementedError as e:
logging(e)
return self.rest.list(namespace)

def get(self, name, namespace):
blockdevice = self.blockdevice.get(name, namespace)
return blockdevice
try:
return self.crd.get(name, namespace)
except NotImplementedError as e:
logging(e)
return self.rest.get(name, namespace)

def provision_longhorn_storage(self, name, engine_version, namespace):
self.blockdevice.provision_longhorn_storage(name, engine_version, namespace)
try:
self.crd.provision_longhorn_storage(name, engine_version, namespace)
except NotImplementedError as e:
logging(e)
self.rest.provision_longhorn_storage(name, engine_version, namespace)
1 change: 1 addition & 0 deletions harvester_robot_tests/libs/blockdevice/crd.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class CRD(Base):

def __init__(self):
"""Initialize Kubernetes client"""
super().__init__()
self.core_api = client.CoreV1Api()
self.custom_api = client.CustomObjectsApi()
self.common_parameters = {
Expand Down
14 changes: 10 additions & 4 deletions harvester_robot_tests/libs/blockdevice/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@
Layer 4: Component and its implementation
"""

from utility.utility import get_harvester_api_client
from .base import Base


class Rest(Base):
"""REST implementation for Blockdevice operations using Harvester API"""

def __init__(self):
self.api_client = get_harvester_api_client()
self.port_forward_process = None
super().__init__()

def list(self, namespace):
return super().list(namespace)

def get(self, name, namespace):
return super().get(name, namespace)

def provision_longhorn_storage(self, name, engine_version, namespace):
return super().provision_longhorn_storage(name, engine_version, namespace)
Loading
Loading