Merge "Add openstack stack create/delete test and template"

This commit is contained in:
Zuul
2025-06-24 15:34:53 +00:00
committed by Gerrit Code Review
30 changed files with 1803 additions and 0 deletions

View File

@@ -67,3 +67,17 @@ class YamlKeywords(BaseKeyword):
FileKeywords(self.ssh_connection).upload_file(rendered_yaml_file_location, target_remote_file)
return target_remote_file
return rendered_yaml_file_location
def load_yaml(self, file_path):
"""
This function will load a yaml file from the local dir
Args:
file_path (str): Path to the YAML file.
Example: 'resources/cloud_platform/folder/file_name'.
Returns:
file: The loaded YAML file
"""
with open(file_path, 'r') as file:
return yaml.safe_load(file)

View File

@@ -0,0 +1,5 @@
def source_admin_openrc(cmd: str):
return f"source /var/opt/openstack/admin-openrc;/var/opt/openstack/clients-wrapper.sh {cmd}"
def source_sudo_admin_openrc(cmd: str):
return f"bash -c '/var/opt/openstack/admin-openrc;/var/opt/openstack/clients-wrapper.sh {cmd}'"

View File

@@ -0,0 +1,29 @@
import json
class OpenstackJsonParser:
"""
Class for Openstack json parsing
Sample JSON:
{
"ID": "1bb26a3c-5a7a-4eb4-8c70-73ef4b93327d",
"Stack Name": "stack_test",
"Project": "f42e14df49524f8eb1fd0665b21122cd",
"Stack Status": "CREATE_COMPLETE",
"Creation Time": "2025-04-10T12:31:03Z",
"Updated Time": null
}
"""
def __init__(self, system_output):
openstack_stack_output_string = ''.join(system_output)
json_part = openstack_stack_output_string.split('\n', 1)[1]
self.system_output = json_part
def get_output_values_list(self):
"""
Getter for output values list
Returns: the output values list
"""
return json.loads(self.system_output)

View File

@@ -0,0 +1,29 @@
import json
from keywords.openstack.openstack.stack.object.openstack_stack_heat_default_enum import OpenstackStackHeatDefaultEnum
class OpenstackReplacementDictParser:
"""
Class for Auxiliary Replacement dictionary for helping with the manage stack
"""
def __init__(self, config, default_json_name):
self.config = config
self.default_json_name = getattr(OpenstackStackHeatDefaultEnum, default_json_name.upper())
self.default_json = json.loads(self.default_json_name.value)
self.replacement_dict = {}
self.keys_to_exclude = OpenstackStackHeatDefaultEnum.KEEP_JSON.value
self.flatten_dict(self.default_json)
def flatten_dict(self, d):
for key, value in d.items():
if isinstance(value, dict) and key not in self.keys_to_exclude:
self.flatten_dict(value)
else:
if key in self.config:
self.replacement_dict[key] = self.config[key]
else:
self.replacement_dict[key] = value
def get_replacement_dict(self):
return self.replacement_dict

View File

@@ -0,0 +1,32 @@
class OpenstackManageStackCreateInput:
"""
Class to support the parameters for managing OpenStack stacks.
"""
def __init__(self):
self.resource_list = None
self.file_destination = None
def set_resource_list(self, resource_list: dict):
"""
Setter for the 'resource_list' property.
"""
self.resource_list = resource_list
def get_resource_list(self) -> dict:
"""
Getter for the 'resource_list' property.
"""
return self.resource_list
def set_file_destination(self, file_destination: str):
"""
Setter for the 'file_destination' property.
"""
self.file_destination = file_destination
def get_file_destination(self) -> str:
"""
Getter for the 'file_destination' property.
"""
return self.file_destination

View File

@@ -0,0 +1,32 @@
class OpenstackManageStackDeleteInput:
"""
Class to support the parameters for managing OpenStack stacks.
"""
def __init__(self):
self.resource_list = None
self.skip_list = []
def set_resource_list(self, resource_list: dict):
"""
Setter for the 'resource_list' property.
"""
self.resource_list = resource_list
def get_resource_list(self) -> dict:
"""
Getter for the 'resource_list' property.
"""
return self.resource_list
def set_skip_list(self, skip_list: list):
"""
Setter for the 'skip_list' property.
"""
self.skip_list = skip_list
def get_skip_list(self) -> list:
"""
Getter for the 'skip_list' property.
"""
return self.skip_list

View File

@@ -0,0 +1,66 @@
class OpenstackStackCreateInput:
"""
Class to support the parameters for 'openstack stack create' command.
An example of using this command is:
'openstack stack create --template=file/location/stack.yaml'
This class is able to generate this command just by previously setting the parameters.
"""
def __init__(self):
"""
Constructor
"""
self.stack_name = None
self.template_file_name = None
self.template_file_path = 'heat'
self.return_format = 'json'
def set_stack_name(self, stack_name: str):
"""
Setter for the 'stack_name' parameter.
"""
self.stack_name = stack_name
def get_stack_name(self) -> str:
"""
Getter for this 'stack_name' parameter.
"""
return self.stack_name
def set_template_file_name(self, template_file_name: str):
"""
Setter for the 'template_file_name' parameter.
"""
self.template_file_name = template_file_name
def get_template_file_name(self) -> str:
"""
Getter for this 'template_file_name' parameter.
"""
return self.template_file_name
def set_template_file_path(self, template_file_path: str):
"""
Setter for the 'template_file_path' parameter.
"""
self.template_file_path = template_file_path
def get_template_file_path(self) -> str:
"""
Getter for this 'template_file_path' parameter.
"""
return self.template_file_path
def set_return_format(self, return_format: str) -> str:
"""
Getter for this 'get_return_format' parameter.
"""
self.return_format = return_format
def get_return_format(self) -> str:
"""
Getter for this 'return_format' parameter.
"""
return self.return_format

View File

@@ -0,0 +1,30 @@
class OpenstackStackDeleteInput:
"""
Class to support the parameters for 'openstack stack delete' command.
An example of using this command is:
'openstack stack delete hello-kitty'
Where:
hello-kitty: the name of the application that you want to delete.
"""
def __init__(self):
"""
Constructor
"""
self.app_name = None
self.force_deletion = False
def get_stack_name(self) -> str:
"""
Getter for this 'stack_name' parameter.
"""
return self.stack_name
def set_stack_name(self, stack_name: str):
"""
Setter for the 'stack_name' parameter.
"""
self.stack_name = stack_name

View File

@@ -0,0 +1,84 @@
from enum import Enum
import json
class OpenstackStackHeatDefaultEnum(Enum):
KEEP_JSON = ["extra_specs"]
PROJECT = json.dumps({
"name": "ace_test_1",
"description": "Ace 1 Project",
"quota": {
"network": 8,
"subnet": 18,
"port": 83,
"floatingip": 10,
"instances": 10,
"cores": 30,
"volumes": 12,
"snapshots": 12
}
})
USER = json.dumps({
"name": "ace_user_1",
"password": "password",
"email": "ace_user_1@noreply.com",
"default_project": "ace_test_1"
})
ROLE_ASSIGNMENT = json.dumps({
"name": "ace_one_admin_ace_test_1",
"role": "admin",
"project": "ace_test_1",
"user": "ace_one"
})
FLAVOR = json.dumps({
"name": "ace_small",
"ram": 1024,
"disk": 2,
"vcpus": 1,
"extra_specs": {
"hw:mem_page_size": "large"
}
})
WEBIMAGE = json.dumps({
"name": "ace_cirros",
"container_format": "bare",
"disk_format": "raw",
"location": "http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img",
"min_disk": 0,
"min_ram": 0,
"visibility": "public"
})
QOS = json.dumps({
"name": "external-qos",
"description": "External Network Policy",
"project_name": "",
"max_kbps": "10000",
"max_burst_kbps": "1000",
"dscp_mark": "16"
})
NETWORK_SEGMENT = json.dumps({
"name": "ace0-ext0-r0-0",
"network_type": "vlan",
"physical_network": "group0",
"minimum_range": "10",
"maximum_range": "10",
"shared": "true",
"private": "false",
"project_name": ""
})
NETWORK = json.dumps({
"name": "external-net0",
"subnet_name": "external-subnet0",
"project_name": "admin",
"qos_policy_name": "external-qos",
"provider_network_type": "vlan",
"provider_physical_network": "group0-ext0",
"shared": "true",
"external": "true",
"gateway_ip": "10.10.85.1",
"enable_dhcp": "false",
"subnet_range": "10.10.85.0/24",
"ip_version": "4",
"allocation_pool_start": "",
"allocation_pool_end": ""
})

View File

@@ -0,0 +1,76 @@
class OpenstackStackListObject:
"""
Class to handle the data provided by the 'openstack stack list' command execution. This command generates the
output table shown below, where each object of this class represents a single row in that table.
+----------+------------+----------+-----------------+----------------------+--------------+
| ID | Stack Name | Project | Stack Status | Creation Time | Updated Time |
+----------+------------+----------+-----------------+----------------------+--------------+
| 1bb26a3c | stack_test | a3c1bb26 | CREATE_COMPLETE | 2025-04-10T12:31:03Z | None |
+----------+------------+----------+-----------------+----------------------+--------------+
"""
def __init__(
self,
id: str,
stack_name: str,
project: str,
stack_status: str,
creation_time: str,
updated_time: str,
):
self.id = id
self.stack_name = stack_name
self.project = project
self.stack_status = stack_status
self.creation_time = creation_time
self.updated_time = updated_time
def get_id(self) -> str:
"""
Getter for id
Returns: the id
"""
return self.id
def get_stack_name(self) -> str:
"""
Getter for stack name
Returns: the stack name
"""
return self.stack_name
def get_project(self) -> str:
"""
Getter for project
Returns: the project
"""
return self.project
def get_stack_status(self) -> str:
"""
Getter for stack status
Returns: the stack status
"""
return self.stack_status
def get_creation_time(self) -> str:
"""
Getter for creation time
Returns: the creation time
"""
return self.creation_time
def get_updated_time(self) -> str:
"""
Getter for updated time
Returns: the updated time
"""
return self.updated_time

View File

@@ -0,0 +1,112 @@
from framework.exceptions.keyword_exception import KeywordException
from framework.logging.automation_logger import get_logger
from keywords.openstack.openstack.stack.object.openstack_stack_list_object import OpenstackStackListObject
from keywords.openstack.openstack.openstack_json_parser import OpenstackJsonParser
class OpenstackStackListOutput:
"""
This class parses the output of the command 'openstack stack list'
The parsing result is a 'OpenstackStackListObject' instance.
Example:
'openstack stack list'
+----------+------------+----------+-----------------+----------------------+--------------+
| ID | Stack Name | Project | Stack Status | Creation Time | Updated Time |
+----------+------------+----------+-----------------+----------------------+--------------+
| 1bb26a3c | stack_test | a3c1bb26 | CREATE_COMPLETE | 2025-04-10T12:31:03Z | None |
+----------+------------+----------+-----------------+----------------------+--------------+
"""
def __init__(self, openstack_stack_list_output):
"""
Constructor
Args:
openstack_stack_list_output: the output of the command 'openstack stack list'.
"""
self.openstack_stacks: [OpenstackStackListObject] = []
openstack_json_parser = OpenstackJsonParser(openstack_stack_list_output)
output_values = openstack_json_parser.get_output_values_list()
for value in output_values:
if self.is_valid_output(value):
self.openstack_stacks.append(
OpenstackStackListObject(
value['ID'],
value['Stack Name'],
value['Project'],
value['Stack Status'],
value['Creation Time'],
value['Updated Time'],
)
)
else:
raise KeywordException(f"The output line {value} was not valid")
def get_stacks(self) -> [OpenstackStackListObject]:
"""
Returns the list of stack objects
Returns:
"""
return self.openstack_stacks
def get_stack(self, stack_name: str) -> OpenstackStackListObject:
"""
Gets the given stack
Args:
stack_name (): the name of the stack
Returns: the stack object
"""
stacks = list(filter(lambda stack: stack.get_stack_name() == stack_name, self.openstack_stacks))
if len(stacks) == 0:
raise KeywordException(f"No stack with name {stack_name} was found.")
return stacks[0]
@staticmethod
def is_valid_output(value):
"""
Checks to ensure the output has the correct keys
Args:
value (): the value to check
Returns:
"""
valid = True
if 'ID' not in value:
get_logger().log_error(f'id is not in the output value: {value}')
valid = False
if 'Stack Name' not in value:
get_logger().log_error(f'stack name is not in the output value: {value}')
valid = False
if 'Project' not in value:
get_logger().log_error(f'project is not in the output value: {value}')
valid = False
if 'Stack Status' not in value:
get_logger().log_error(f'stack status is not in the output value: {value}')
valid = False
if 'Creation Time' not in value:
get_logger().log_error(f'creation time is not in the output value: {value}')
valid = False
return valid
def is_in_stack_list(self, stack_name: str) -> bool:
"""
Verifies if there is an stack with the name 'stack_name'.
Args:
stack_name (str): a string representing the stack's name.
Returns:
bool: True if there is a stack with the name 'stack_name'; False otherwise.
"""
stacks = list(filter(lambda stack: stack.get_stack() == stack_name, self.openstack_stacks))
if len(stacks) == 0:
return False
return True

View File

@@ -0,0 +1,112 @@
class OpenstackStackObject:
"""
Class to handle data provided by commands such as 'openstack stack create'
Example:
'openstack stack create --template=heat/project_stack.yaml stack_test -f json'
{
"id": "1bb26a3c",
"stack_name": "stack_test",
"description": "Heat template to create OpenStack projects.\n",
"creation_time": "2025-04-10T13:35:04Z",
"updated_time": null,
"stack_status": "CREATE_COMPLETE",
"stack_status_reason": "Stack CREATE completed successfully"
}
"""
def __init__(self):
"""
Constructor.
"""
self.id: str
self.stack_name: str
self.description: str
self.creation_time: str
self.updated_time: str
self.stack_status: str
self.stack_status_reason: str
def set_id(self, id: str):
"""
Setter for the 'id' property.
"""
self.id = id
def get_id(self) -> str:
"""
Getter for this 'id' property.
"""
return self.id
def set_stack_name(self, stack_name: str):
"""
Setter for the 'stack_name' property.
"""
self.stack_name = stack_name
def get_stack_name(self) -> str:
"""
Getter for the 'stack_name' property.
"""
return self.stack_name
def set_description(self, description: str):
"""
Setter for the 'description' property.
"""
self.description = description
def get_description(self) -> str:
"""
Getter for the 'description' property.
"""
return self.description
def set_creation_time(self, creation_time: str):
"""
Setter for the 'creation_time' property.
"""
self.creation_time = creation_time
def get_creation_time(self) -> str:
"""
Getter for the 'creation_time' property.
"""
return self.creation_time
def set_updated_time(self, updated_time: str):
"""
Setter for the 'updated_time' property.
"""
self.updated_time = updated_time
def get_updated_time(self) -> str:
"""
Getter for the 'updated_time' property.
"""
return self.updated_time
def set_stack_status(self, stack_status: str):
"""
Setter for the 'stack_status' property.
"""
self.stack_status = stack_status
def get_stack_status(self) -> str:
"""
Getter for the 'stack_status' property.
"""
return self.stack_status
def set_stack_status_reason(self, stack_status_reason: str):
"""
Setter for the 'stack_status_reason' property.
"""
self.stack_status_reason = stack_status_reason
def get_stack_status_reason(self) -> str:
"""
Getter for the 'stack_status_reason' property.
"""
return self.stack_status_reason

View File

@@ -0,0 +1,66 @@
from keywords.openstack.openstack.stack.object.openstack_stack_object import OpenstackStackObject
from keywords.openstack.openstack.openstack_json_parser import OpenstackJsonParser
class OpenstackStackOutput:
"""
This class parses the output of commands such as 'openstack stack create'
that share the same output as shown in the example below.
The parsing result is a 'openstackStackObject' instance.
Example:
'openstack stack create --template=heat/project_stack.yaml stack_test -f json'
{
"id": "1bb26a3c",
"stack_name": "stack_test",
"description": "Heat template to create OpenStack projects.\n",
"creation_time": "2025-04-10T13:35:04Z",
"updated_time": null,
"stack_status": "CREATE_COMPLETE",
"stack_status_reason": "Stack CREATE completed successfully"
}
"""
def __init__(self, openstack_stack_output):
"""
Constructor.
Create an internal OpenstackStackCreateObject from the passed parameter.
Args:
openstack_stack_output (list[str]): a list of strings representing the output of the
'openstack stack create' command.
"""
openstack_json_parser = OpenstackJsonParser(openstack_stack_output)
output_values = openstack_json_parser.get_output_values_list()
self.openstack_stack_object = OpenstackStackObject()
if 'id' in output_values:
self.openstack_stack_object.set_id(output_values['id'])
if 'stack_name' in output_values:
self.openstack_stack_object.set_stack_name(output_values['stack_name'])
if 'description' in output_values:
self.openstack_stack_object.set_description(output_values['description'])
if 'creation_time' in output_values:
self.openstack_stack_object.set_creation_time(output_values['creation_time'])
if 'updated_time' in output_values:
self.openstack_stack_object.set_updated_time(output_values['updated_time'])
if 'stack_status' in output_values:
self.openstack_stack_object.set_stack_status(output_values['stack_status'])
if 'stack_status_reason' in output_values:
self.openstack_stack_object.set_stack_status_reason(output_values['stack_status_reason'])
def get_openstack_stack_object(self) -> OpenstackStackObject:
"""
Getter for OpenstackStackObject object.
Returns:
A OpenstackStackObject instance representing the output of commands sucha as 'openstack stack create'.
"""
return self.openstack_stack_object

View File

@@ -0,0 +1,16 @@
from enum import Enum
class OpenstackStackStatusEnum(Enum):
CREATE_IN_PROGRESS = "create_in_progress"
CREATE_COMPLETE = "create_complete"
CREATE_FAILED = "create_failed"
DELETE_IN_PROGRESS = "delete_in_progress"
DELETE_COMPLETE = "delete_complete"
DELETE_FAILED = "delete_failed"
UPDATE_IN_PROGRESS = "update_in_progress"
UPDATE_COMPLETE = "update_complete"
UPDATE_FAILED = "update_failed"
ROLLBACK_IN_PROGRESS = "rollback_in_progress"
ROLLBACK_COMPLETE = "rollback_complete"
ROLLBACK_FAILED = "rollback_failed"

View File

@@ -0,0 +1,66 @@
class OpenstackStackUpdateInput:
"""
Class to support the parameters for 'openstack stack update' command.
An example of using this command is:
'openstack stack update --template=file/location/stack.yaml'
This class is able to generate this command just by previously setting the parameters.
"""
def __init__(self):
"""
Constructor
"""
self.stack_name = None
self.template_file_name = None
self.template_file_path = None
self.return_format = 'json'
def set_stack_name(self, stack_name: str):
"""
Setter for the 'stack_name' parameter.
"""
self.stack_name = stack_name
def get_stack_name(self) -> str:
"""
Getter for this 'stack_name' parameter.
"""
return self.stack_name
def set_template_file_name(self, template_file_name: str):
"""
Setter for the 'template_file_name' parameter.
"""
self.template_file_name = template_file_name
def get_template_file_name(self) -> str:
"""
Getter for this 'template_file_name' parameter.
"""
return self.template_file_name
def set_template_file_path(self, template_file_path: str):
"""
Setter for the 'template_file_path' parameter.
"""
self.template_file_path = template_file_path
def get_template_file_path(self) -> str:
"""
Getter for this 'template_file_path' parameter.
"""
return self.template_file_path
def set_return_format(self, return_format: str) -> str:
"""
Getter for this 'get_return_format' parameter.
"""
self.return_format = return_format
def get_return_format(self) -> str:
"""
Getter for this 'return_format' parameter.
"""
return self.return_format

View File

@@ -0,0 +1,88 @@
from framework.resources.resource_finder import get_stx_resource_path
from framework.logging.automation_logger import get_logger
from keywords.openstack.openstack.openstack_replacement_dict_parser import OpenstackReplacementDictParser
from keywords.files.yaml_keywords import YamlKeywords
from keywords.openstack.openstack.stack.object.openstack_manage_stack_create_input import OpenstackManageStackCreateInput
from keywords.openstack.openstack.stack.object.openstack_manage_stack_delete_input import \
OpenstackManageStackDeleteInput
from keywords.openstack.openstack.stack.object.openstack_stack_delete_input import OpenstackStackDeleteInput
from keywords.openstack.openstack.stack.openstack_stack_create_keywords import OpenstackStackCreateKeywords
from keywords.openstack.openstack.stack.object.openstack_stack_create_input import OpenstackStackCreateInput
from keywords.openstack.openstack.stack.openstack_stack_delete_keywords import OpenstackStackDeleteKeywords
from keywords.openstack.openstack.stack.openstack_stack_list_keywords import OpenstackStackListKeywords
class OpenstackManageStack:
"""
Class for Managing stacks support
"""
def __init__(self, ssh_connection):
self.ssh_connection = ssh_connection
def create_stacks(self, openstack_manage_stack_create_input: OpenstackManageStackCreateInput):
"""
Executes a sequence of stack_create commands based on a OpenstackManageStackCreateInput given
Validates if given stack is already created before creating it again
Args: OpenstackManageStackCreateInput
Returns: None
"""
resource_list = openstack_manage_stack_create_input.get_resource_list()
file_destination_location = openstack_manage_stack_create_input.get_file_destination()
if resource_list is None or file_destination_location is None:
error_message = "resource_list and file_destination_location are required"
get_logger().log_exception(error_message)
raise ValueError(error_message)
for key, values in resource_list.items():
for value in values:
stack_name = f"{key}_{value['name']}"
if OpenstackStackCreateKeywords.is_already_created(stack_name):
continue
output_file_name = f"{stack_name}.yaml"
template_file = get_stx_resource_path(f"resources/openstack/stack/template/{key}.yaml")
replacement_dictionary = OpenstackReplacementDictParser(value, key).get_replacement_dict()
YamlKeywords(self.ssh_connection).generate_yaml_file_from_template(
template_file, replacement_dictionary,
output_file_name,
file_destination_location
)
openstack_stack_create = OpenstackStackCreateInput()
openstack_stack_create.set_stack_name(stack_name)
openstack_stack_create.set_template_file_name(output_file_name)
OpenstackStackCreateKeywords(self.ssh_connection).openstack_stack_create(openstack_stack_create)
def delete_stacks(self, openstack_manage_stack_delete_input: OpenstackManageStackDeleteInput):
"""
Executes a sequence of stack_delete commands based on a OpenstackManageStackDeleteInput given
Will not delete stacks listed in the skip_list of that ManageStackDeleteInput
Gets all existing stacks and if a stack with the given file exists, it will delete it
Args: OpenstackManageStackDeleteInput
Returns: None
"""
resource_list = openstack_manage_stack_delete_input.get_resource_list()
skip_list = openstack_manage_stack_delete_input.get_skip_list()
if resource_list is None:
error_message = "resource_list is required"
get_logger().log_exception(error_message)
raise ValueError(error_message)
openstack_stack_list = OpenstackStackListKeywords(self.ssh_connection).get_openstack_stack_list().get_stacks()
for key, values in resource_list.items():
if key in skip_list:
continue
for value in values:
stack_name = f"{key}_{value['name']}"
if stack_name in openstack_stack_list:
openstack_stack_delete_input = OpenstackStackDeleteInput()
openstack_stack_delete_input.set_stack_name(stack_name)
OpenstackStackDeleteKeywords(self.ssh_connection).get_openstack_stack_delete(openstack_stack_delete_input)

View File

@@ -0,0 +1,117 @@
from framework.logging.automation_logger import get_logger
from keywords.base_keyword import BaseKeyword
from keywords.openstack.command_wrappers import source_admin_openrc
from keywords.openstack.openstack.stack.object.openstack_stack_create_input import OpenstackStackCreateInput
from keywords.openstack.openstack.stack.openstack_stack_list_keywords import OpenstackStackListKeywords
from keywords.openstack.openstack.stack.object.openstack_stack_output import OpenstackStackOutput
from keywords.openstack.openstack.stack.object.openstack_stack_status_enum import OpenstackStackStatusEnum
from keywords.python.string import String
class OpenstackStackCreateKeywords(BaseKeyword):
"""
Class for Openstack Stack Create
"""
def __init__(self, ssh_connection):
"""
Constructor
Args:
ssh_connection:
"""
self.ssh_connection = ssh_connection
def openstack_stack_create(self, openstack_stack_create_input: OpenstackStackCreateInput) -> OpenstackStackOutput:
"""
Openstack stack create function, creates a given stack
Args:
openstack_stack_create_input (OpenstackStackCreateInput): an object with the required parameters
Returns:
openstack_stack_output: An object with the openstack stack create output
"""
# Gets the command 'openstack stack create' with its parameters configured.
cmd = self.get_command(openstack_stack_create_input)
stack_name = openstack_stack_create_input.get_stack_name()
# Executes the command 'openstack stack create'.
output = self.ssh_connection.send(source_admin_openrc(cmd),get_pty=True)
self.validate_success_return_code(self.ssh_connection)
openstack_stack_output = OpenstackStackOutput(output)
# Tracks the execution of the command 'openstack stack create' until its completion or a timeout.
openstack_stack_list_keywords = OpenstackStackListKeywords(self.ssh_connection)
openstack_stack_list_keywords.validate_stack_status(stack_name, 'CREATE_COMPLETE')
# If the execution arrived here the status of the stack is 'created'.
openstack_stack_output.get_openstack_stack_object().set_stack_status('create_complete')
return openstack_stack_output
def is_already_created(self, stack_name: str) -> bool:
"""
Verifies if the stack has already been created.
Args:
stack_name (str): a string representing the name of the stack.
Returns:
bool: True if the stack named 'stack_name' has already been created; False otherwise.
"""
try:
openstack_stack_list_keywords = OpenstackStackListKeywords(self.ssh_connection)
if openstack_stack_list_keywords.get_openstack_stack_list().is_in_stack_list(stack_name):
stack = OpenstackStackListKeywords(self.ssh_connection).get_openstack_stack_list().get_stack(stack_name)
return (
stack.get_stack_status() == OpenstackStackStatusEnum.CREATE_IN_PROGRESS.value
or stack.get_stack_status() == OpenstackStackStatusEnum.CREATE_COMPLETE.value
or stack.get_stack_status() == OpenstackStackStatusEnum.UPDATE_IN_PROGRESS.value
or stack.get_stack_status() == OpenstackStackStatusEnum.UPDATE_COMPLETE.value
or stack.get_stack_status() == OpenstackStackStatusEnum.ROLLBACK_IN_PROGRESS.value
or stack.get_stack_status() == OpenstackStackStatusEnum.ROLLBACK_COMPLETE.value
)
return False
except Exception as ex:
get_logger().log_exception(f"An error occurred while verifying whether the application named {stack_name} is already created.")
raise ex
def get_command(self, openstack_stack_create_input: OpenstackStackCreateInput) -> str:
"""
Generates a string representing the 'openstack stack create' command with parameters based on the values in
the 'openstack_stack_create_input' argument.
Args:
openstack_stack_create_input (OpenstackStackCreateInput): an instance of OpenstackStackCreateInput
configured with the parameters needed to execute the 'openstack stack create' command properly.
Returns:
str: a string representing the 'openstack stack create' command, configured according to the parameters
in the 'openstack_stack_create_input' argument.
"""
# 'template_file_path' and 'template_file_name' properties are required
template_file_path = openstack_stack_create_input.get_template_file_path()
template_file_name = openstack_stack_create_input.get_template_file_name()
if String.is_empty(template_file_path) or String.is_empty(template_file_name):
error_message = "Template path and name must be specified"
get_logger().log_exception(error_message)
raise ValueError(error_message)
template_file_path_as_param = (
f'--template={openstack_stack_create_input.get_template_file_path()}/{openstack_stack_create_input.get_template_file_name()}'
)
# 'stack_name' is required
stack_name = openstack_stack_create_input.get_stack_name()
if String.is_empty(stack_name):
error_message = "Stack name is required"
get_logger().log_exception(error_message)
raise ValueError(error_message)
# 'return_format' property is optional.
return_format_as_param = f'-f {openstack_stack_create_input.get_return_format()}'
# Assembles the command.
cmd = f'openstack stack create {return_format_as_param} {template_file_path_as_param} {stack_name}'
return cmd

View File

@@ -0,0 +1,50 @@
from keywords.base_keyword import BaseKeyword
from keywords.openstack.command_wrappers import source_admin_openrc
from keywords.openstack.openstack.stack.object.openstack_stack_delete_input import OpenstackStackDeleteInput
class OpenstackStackDeleteKeywords(BaseKeyword):
"""
Class for Openstack stack delete keywords
"""
def __init__(self, ssh_connection):
"""
Constructor
Args:
ssh_connection:
"""
self.ssh_connection = ssh_connection
def get_openstack_stack_delete(self, openstack_stack_delete_input: OpenstackStackDeleteInput) -> str:
"""
Delete a stack specified in the parameter 'openstack_stack_delete_input'.
Args:
openstack_stack_delete_input (OpenstackStackDeleteInput): defines the stack name
Returns:
str: a string message indicating the result of the deletion. Examples:
'Stack hello-kitty deleted.\n'
'Stack-delete rejected: stack not found.\n'
"""
cmd = self.get_command(openstack_stack_delete_input)
output = self.ssh_connection.send(source_admin_openrc(cmd),get_pty=True)
self.validate_success_return_code(self.ssh_connection)
return output[0]
def get_command(self, openstack_stack_delete_input: OpenstackStackDeleteInput) -> str:
"""
Generates a string representing the 'openstack stack delete' command with parameters based on the values in
the 'openstack_stack_delete_input' argument.
Args:
openstack_stack_delete_input (OpenstackStackDeleteInput): an instance of OpenstackStackDeleteInput
configured with the parameters needed to execute the 'openstack stack delete' command properly.
Returns:
str: a string representing the 'openstack stack delete' command, configured according to the parameters
in the 'openstack_stack_delete_input' argument.
"""
cmd = f'openstack stack delete -y {openstack_stack_delete_input.get_stack_name()}'
return cmd

View File

@@ -0,0 +1,55 @@
from framework.logging.automation_logger import get_logger
from framework.validation.validation import validate_equals_with_retry
from keywords.base_keyword import BaseKeyword
from keywords.openstack.command_wrappers import source_admin_openrc
from keywords.openstack.openstack.stack.object.openstack_stack_list_output import OpenstackStackListOutput
class OpenstackStackListKeywords(BaseKeyword):
"""
Class for Openstack stack list keywords.
"""
def __init__(self, ssh_connection):
"""
Constructor
Args:
ssh_connection:
"""
self.ssh_connection = ssh_connection
def get_openstack_stack_list(self) -> OpenstackStackListOutput:
"""
Gets a OpenstackStackListOutput object related to the execution of the 'openstack stack list' command.
Args: None
Returns:
OpenstackStackListOutput: an instance of the OpenstackStackListOutput object representing the
heat stacks on the host, as a result of the execution of the 'openstack stack list' command.
"""
output = self.ssh_connection.send(source_admin_openrc('openstack stack list -f json'),get_pty=True)
self.validate_success_return_code(self.ssh_connection)
openstack_stack_list_output = OpenstackStackListOutput(output)
return openstack_stack_list_output
def validate_stack_status(self, stack_name: str, status: str):
"""
This function will validate that the stack specified reaches the desired status.
Args:
stack_name: Name of the stack that we are waiting for.
status: Status in which we want to wait for the stack to reach.
Returns: None
"""
def get_stack_status():
openstack_stacks = self.get_openstack_stack_list()
stack_status = openstack_stacks.get_stack(stack_name).get_stack_status()
return stack_status
message = f"Openstack stack {stack_name}'s status is {status}"
validate_equals_with_retry(get_stack_status, status, message, timeout=300)

View File

@@ -0,0 +1,109 @@
from framework.logging.automation_logger import get_logger
from keywords.base_keyword import BaseKeyword
from keywords.openstack.command_wrappers import source_admin_openrc
from keywords.openstack.openstack.stack.object.openstack_stack_update_input import OpenstackStackUpdateInput
from keywords.openstack.openstack.stack.openstack_stack_list_keywords import OpenstackStackListKeywords
from keywords.openstack.openstack.stack.object.openstack_stack_output import OpenstackStackOutput
from keywords.openstack.openstack.stack.object.openstack_stack_status_enum import OpenstackStackStatusEnum
from keywords.python.string import String
class OpenstackStackUpdateKeywords(BaseKeyword):
"""
Class for Openstack Stack Update
"""
def __init__(self, ssh_connection):
"""
Constructor
Args:
ssh_connection:
"""
self.ssh_connection = ssh_connection
def openstack_stack_update(self, openstack_stack_update_input: OpenstackStackUpdateInput) -> OpenstackStackOutput:
"""
Openstack stack update function, updates a given existing stack
Args:
openstack_stack_update_input (OpenstackStackUpdateInput): an object with the required parameters
Returns:
openstack_stack_output: An object with the openstack stack update output
"""
# Gets the command 'openstack stack update' with its parameters configured.
cmd = self.get_command(openstack_stack_update_input)
stack_name = openstack_stack_update_input.get_stack_name()
# Executes the command 'openstack stack update'.
output = self.ssh_connection.send(source_admin_openrc(cmd), get_pty=True)
self.validate_success_return_code(self.ssh_connection)
openstack_stack_output = OpenstackStackOutput(output)
# Tracks the execution of the command 'openstack stack update' until its completion or a timeout.
openstack_stack_list_keywords = OpenstackStackListKeywords(self.ssh_connection)
openstack_stack_list_keywords.validate_stack_status(stack_name, 'UPDATE_COMPLETE')
# If the execution arrived here the status of the stack is 'updated'.
openstack_stack_output.get_openstack_stack_object().set_stack_status('update_complete')
return openstack_stack_output
def is_already_updated(self, stack_name: str) -> bool:
"""
Verifies if the stack has already been updated.
Args:
stack_name (str): a string representing the name of the stack.
Returns:
bool: True if the stack named 'stack_name' has already been updated; False otherwise.
"""
openstack_stack_list_keywords = OpenstackStackListKeywords(self.ssh_connection)
if openstack_stack_list_keywords.get_openstack_stack_list().is_in_stack_list(stack_name):
stack = OpenstackStackListKeywords(self.ssh_connection).get_openstack_stack_list().get_stack(stack_name)
return (
stack.get_stack_status() == OpenstackStackStatusEnum.UPDATE_IN_PROGRESS.value
or stack.get_stack_status() == OpenstackStackStatusEnum.UPDATE_COMPLETE.value
)
return False
def get_command(self, openstack_stack_update_input: OpenstackStackUpdateInput) -> str:
"""
Generates a string representing the 'openstack stack update' command with parameters based on the values in
the 'openstack_stack_update_input' argument.
Args:
openstack_stack_update_input (OpenstackStackUpdateInput): an instance of OpenstackStackUpdateInput
configured with the parameters needed to execute the 'openstack stack update' command properly.
Returns:
str: a string representing the 'openstack stack update' command, configured according to the parameters
in the 'openstack_stack_update_input' argument.
"""
# 'template_file_path' and 'template_file_name' properties are required
template_file_path = openstack_stack_update_input.get_template_file_path()
template_file_name = openstack_stack_update_input.get_template_file_name()
if String.is_empty(template_file_path) or String.is_empty(template_file_name):
error_message = "Template path and name must be specified"
get_logger().log_exception(error_message)
raise ValueError(error_message)
template_file_path_as_param = (
f'--template={openstack_stack_update_input.get_template_file_path()}/{openstack_stack_update_input.get_template_file_name()}'
)
# 'stack_name' is required
stack_name = openstack_stack_update_input.get_stack_name()
if String.is_empty(stack_name):
error_message = "Stack name is required"
get_logger().log_exception(error_message)
raise ValueError(error_message)
# 'return_format' property is optional.
return_format_as_param = f'-f {openstack_stack_update_input.get_return_format()}'
# Assembles the command.
cmd = f'openstack stack update {return_format_as_param} {template_file_path_as_param} {stack_name}'
return cmd

View File

@@ -0,0 +1,56 @@
project:
- name: ace_project_1
description: Ace 1 Project
quota:
network: 8
subnet: 18
port: 83
floatingip: 10
instances: 10
cores: 30
volumes: 12
snapshots: 12
- name: ace_project_2
description: Ace 2 Project
quota:
network: 8
subnet: 18
port: 83
floatingip: 10
instances: 10
cores: 30
volumes: 12
snapshots: 12
user:
- name: ace_one
password: password
email: tenant1@noreply.com
default_project: ace_project_1
- name: ace_two
password: password
email: tenant2@noreply.com
default_project: ace_project_2
role_assignment:
- role: admin
name: ace_one_admin_ace_project_1
project: ace_test_1
user: ace_one
- role: admin
name: ace_two_admin_ace_project_2
project: ace_test_2
user: ace_two
webimage:
- name: ace_cirros_image
container_format: bare
disk_format: raw
location: http://download.cirros-cloud.net/0.4.0/cirros-0.4.0-x86_64-disk.img
min_disk: 0
min_ram: 0
visibility: public
flavor:
- name: ace_small
ram: 1024
disk: 2
vcpus: 1
extra_specs:
hw:mem_page_size: large

View File

@@ -0,0 +1,45 @@
heat_template_version: wallaby
description: >
Heat template to create OpenStack flavors.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the flavor
ram:
type: number
default: "{{ ram }}"
description: Amount of RAM in MB
vcpus:
type: number
default: "{{ vcpus }}"
description: Number of VCPUs
disk:
type: number
default: "{{ disk }}"
description: Size of disk in GB
extra_specs:
type: json
default: {{ extra_specs }}
description: Properties of the flavor
resources:
flavor:
type: OS::Nova::Flavor
properties:
name: { get_param: name }
ram: { get_param: ram }
vcpus: { get_param: vcpus }
disk: { get_param: disk }
extra_specs: { get_param: extra_specs }
outputs:
flavor_id:
description: ID of the created flavor
value: { get_resource: flavor }

View File

@@ -0,0 +1,119 @@
heat_template_version: wallaby
description: >
Heat template to create OpenStack networks and subnets with project name and QoS policy name lookup.
parameters:
network_name:
type: string
default: "{{ name }}"
description: Name of the network
subnet_name:
type: string
default: "{{ subnet_name }}"
description: Name of the subnet
project_name:
type: string
default: "{{ project_name }}"
description: Project name associated with the network
qos_policy_name:
type: string
default: "{{ qos_policy_name }}"
description: QoS policy name associated with the network
provider_network_type:
type: string
default: "{{ provider_network_type }}"
description: Provider network type (e.g., vlan)
provider_physical_network:
type: string
default: "{{ provider_physical_network }}"
description: Provider physical network name
shared:
type: boolean
default: "{{ shared }}"
description: Whether the network is shared
external:
type: boolean
default: "{{ external }}"
description: Whether the network is external
gateway_ip:
type: string
default: "{{ gateway_ip }}"
description: Gateway IP for the subnet
enable_dhcp:
type: boolean
default: "{{ enable_dhcp }}"
description: Whether DHCP is disabled for the subnet
subnet_range:
type: string
default: "{{ subnet_range }}"
description: Subnet range in CIDR format
ip_version:
type: number
default: "{{ ip_version }}"
description: IP version (e.g., 4)
allocation_pool_start:
type: string
default: "{{ allocation_pool_start }}"
description: Start of the allocation pool
allocation_pool_end:
type: string
default: "{{ allocation_pool_end }}"
description: End of the allocation pool
resources:
project:
type: OS::Keystone::Project
properties:
name: { get_param: project_name }
qos_policy:
type: OS::Neutron::QoSPolicy
properties:
name: { get_param: qos_policy_name }
network:
type: OS::Neutron::Net
properties:
name: { get_param: network_name }
tenant_id: { get_resource: project }
provider:network_type: { get_param: provider_network_type }
provider:physical_network: { get_param: provider_physical_network }
shared: { get_param: shared }
external: { get_param: external }
qos_policy: { get_resource: qos_policy }
subnet:
type: OS::Neutron::Subnet
properties:
name: { get_param: subnet_name }
network_id: { get_resource: network }
gateway_ip: { get_param: gateway_ip }
enable_dhcp: { get_param: enable_dhcp }
cidr: { get_param: subnet_range }
ip_version: { get_param: ip_version }
allocation_pools:
- start: { get_param: allocation_pool_start }
end: { get_param: allocation_pool_end }
outputs:
network_id:
description: ID of the created network
value: { get_resource: network }
subnet_id:
description: ID of the created subnet
value: { get_resource: subnet }

View File

@@ -0,0 +1,62 @@
heat_template_version: wallaby
description: >
Heat template to create OpenStack network segment ranges with project name lookup.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the network segment range
network_type:
type: string
default: "{{ network_type }}"
description: Network type (e.g., vlan)
physical_network:
type: string
default: "{{ physical_network }}"
description: Physical network name
minimum:
type: number
default: "{{ minimum_range }}"
description: Minimum VLAN ID
maximum:
type: number
default: "{{ maximum_range }}"
description: Maximum VLAN ID
shared:
type: boolean
default: "{{ shared }}"
description: Whether the segment range is shared
project_name:
type: string
default: "{{ project_name }}"
description: Project name associated with the segment range (optional)
resources:
project:
type: OS::Keystone::Project
properties:
name: { get_param: project_name }
network_segment:
type: OS::Neutron::Segment
properties:
name: { get_param: name }
network_type: { get_param: network_type }
physical_network: { get_param: physical_network }
minimum: { get_param: minimum_range }
maximum: { get_param: maximum_range }
shared: { get_param: shared }
project_id: { get_resource: project }
outputs:
segment_range_id:
description: ID of the created network segment range
value: { get_resource: network_segment }

View File

@@ -0,0 +1,90 @@
heat_template_version: wallaby
description: >
Heat template to create a single OpenStack project with quotas.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the project
description:
type: string
default: "{{ description }}"
description: Description of the project
network:
type: number
default: "{{ network }}"
description: Quota for the number of networks
subnet:
type: number
default: "{{ subnet }}"
description: Quota for the number of subnets
port:
type: number
default: "{{ port }}"
description: Quota for the number of ports
floatingip:
type: number
default: "{{ floatingip }}"
description: Quota for the number of floating IPs
instances:
type: number
default: "{{ instances }}"
description: Quota for the number of instances
cores:
type: number
default: "{{ cores }}"
description: Quota for the number of cores
volumes:
type: number
default: "{{ volumes }}"
description: Quota for the number of volumes
snapshots:
type: number
default: "{{ snapshots }}"
description: Quota for the number of snapshots
resources:
project:
type: OS::Keystone::Project
properties:
name: { get_param: name }
description: { get_param: description }
cinder_quota:
type: OS::Cinder::Quota
properties:
project: { get_resource: project }
volumes: { get_param: volumes }
snapshots: { get_param: snapshots }
neutron_quota:
type: OS::Neutron::Quota
properties:
project: { get_resource: project }
network: { get_param: network }
subnet: { get_param: subnet }
port: { get_param: port }
floatingip: { get_param: floatingip }
nova_quota:
type: OS::Nova::Quota
properties:
project: { get_resource: project }
cores: { get_param: cores }
instances: { get_param: instances }
outputs:
project_id:
description: ID of the created project
value: { get_attr: [project, show, id] }

View File

@@ -0,0 +1,61 @@
heat_template_version: wallaby
description: >
Heat template to create OpenStack QoS policies.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the QoS policy
description:
type: string
default: "{{ description }}"
description: Description of the QoS policy
project_name:
type: string
default: "{{ project_name }}"
description: Project ID associated with the QoS policy
max_kbps:
type: number
default: "{{ max_kbps }}"
description: Maximum bandwidth in kbps
max_burst_kbps:
type: number
default: "{{ max_burst_kbps }}"
description: Maximum burst bandwidth in kbps
dscp_mark:
type: number
default: "{{ dscp_mark }}"
description: DSCP marking value
resources:
project:
type: OS::Keystone::Project
properties:
name: { get_param: project_name }
qos_policy:
type: OS::Neutron::QoSPolicy
properties:
name: { get_param: name }
description: { get_param: description }
tenant_id: { get_resource: project }
rules:
- type: OS::Neutron::QoSBandwidthLimitRule
properties:
max_kbps: { get_param: max_kbps }
max_burst_kbps: { get_param: max_burst_kbps }
- type: OS::Neutron::QoSDscpMarkingRule
properties:
dscp_mark: { get_param: dscp_mark }
outputs:
qos_policy_id:
description: ID of the created QoS policy
value: { get_resource: qos_policy }

View File

@@ -0,0 +1,34 @@
heat_template_version: wallaby
description: >
Heat template to assign roles to users in a project.
parameters:
project:
type: string
default: "{{ project }}"
description: Name of the project
user:
type: string
default: "{{ user }}"
description: Name of the user
role:
type: string
default: "{{ role }}"
description: Name of the role to assign
resources:
role_assignment:
type: OS::Keystone::UserRoleAssignment
properties:
user: { get_param: user }
roles:
- project: { get_param: project }
role: { get_param: role }
outputs:
role_assignment_id:
description: ID of the role assignment
value: { get_resource: role_assignment }

View File

@@ -0,0 +1,39 @@
heat_template_version: wallaby
description: >
Heat template to create OpenStack users.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the user
password:
type: string
default: "{{ password }}"
description: Password for the user
email:
type: string
default: "{{ email }}"
description: Email of the user
default_project:
type: string
default: "{{ default_project }}"
description: Name of the project to assign the user to
resources:
user:
type: OS::Keystone::User
properties:
name: { get_param: name }
password: { get_param: password }
email: { get_param: email }
default_project: { get_param: default_project }
outputs:
user_id:
description: ID of the created user
value: { get_resource: user }

View File

@@ -0,0 +1,57 @@
heat_template_version: wallaby
description: >
Heat template to create a Glance image with optional minimum disk and minimum RAM properties.
parameters:
name:
type: string
default: "{{ name }}"
description: Name of the image
container_format:
type: string
default: "{{ container_format }}"
description: Container format of the image
disk_format:
type: string
default: "{{ disk_format }}"
description: Disk format of the image
location:
type: string
default: "{{ location }}"
description: Path to the image file
min_disk:
type: number
description: Minimum disk size (optional)
default: "{{ min_disk }}"
min_ram:
type: number
description: Minimum RAM size (optional)
default: "{{ min_ram }}"
visibility:
type: string
description: Is the image public
default: "{{ visibility }}"
resources:
glance_image:
type: OS::Glance::WebImage
properties:
name: { get_param: name }
container_format: { get_param: container_format }
disk_format: { get_param: disk_format }
visibility: { get_param: visibility }
location: { get_param: location }
min_disk: { get_param: min_disk }
min_ram: { get_param: min_ram }
outputs:
image_id:
description: ID of the created image
value: { get_attr: [glance_image, show, id] }

View File

@@ -0,0 +1,52 @@
from pytest import mark
from framework.resources.resource_finder import get_stx_resource_path
from keywords.cloud_platform.ssh.lab_connection_keywords import LabConnectionKeywords
from keywords.files.file_keywords import FileKeywords
from keywords.files.yaml_keywords import YamlKeywords
from keywords.openstack.openstack.stack.object.openstack_manage_stack_delete_input import \
OpenstackManageStackDeleteInput
from keywords.openstack.openstack.stack.openstack_manage_stack_keywords import OpenstackManageStack
from keywords.openstack.openstack.stack.object.openstack_manage_stack_create_input import OpenstackManageStackCreateInput
from keywords.openstack.openstack.stack.object.openstack_stack_delete_input import OpenstackStackDeleteInput
from keywords.openstack.openstack.stack.openstack_stack_delete_keywords import OpenstackStackDeleteKeywords
from keywords.openstack.openstack.stack.openstack_stack_list_keywords import OpenstackStackListKeywords
@mark.p1
def test_project_stack_create_and_delete():
"""
Tests the build of a simple Openstack lab environment using a YAML file and the openstack stack create command
Test Steps:
- loads a basic YAML file containing environment properties
- connect to active controller
- creates a folder to store generated heat templates
- generates/uploads a heat template file in the remote repository
- goes by each object of that YAML file and create that resource using the uploaded template
and openstack stack create command
- does not generate Neutron related resources
"""
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
heat_file_destination = "/var/opt/openstack/heat"
lab_config_file_location = get_stx_resource_path("resources/openstack/stack/regression/basic_openstack_project_config.yaml")
lab_config = YamlKeywords(ssh_connection).load_yaml(lab_config_file_location)
FileKeywords(ssh_connection).create_directory(heat_file_destination)
openstack_manage_stack_creation = OpenstackManageStackCreateInput()
openstack_manage_stack_creation.set_file_destination(heat_file_destination)
openstack_manage_stack_creation.set_resource_list(lab_config)
OpenstackManageStack(ssh_connection).create_stacks(openstack_manage_stack_creation)
lab_connect_keywords = LabConnectionKeywords()
ssh_connection = lab_connect_keywords.get_active_controller_ssh()
lab_config_file_location = get_stx_resource_path("resources/openstack/stack/regression/basic_openstack_project_config.yaml")
lab_config = YamlKeywords(ssh_connection).load_yaml(lab_config_file_location)
openstack_manage_stack_deletion = OpenstackManageStackDeleteInput()
openstack_manage_stack_deletion.set_resource_list(lab_config)
OpenstackManageStack(ssh_connection).delete_stacks(openstack_manage_stack_deletion)