Merge "Add openstack stack create/delete test and template"
This commit is contained in:
@@ -67,3 +67,17 @@ class YamlKeywords(BaseKeyword):
|
|||||||
FileKeywords(self.ssh_connection).upload_file(rendered_yaml_file_location, target_remote_file)
|
FileKeywords(self.ssh_connection).upload_file(rendered_yaml_file_location, target_remote_file)
|
||||||
return target_remote_file
|
return target_remote_file
|
||||||
return rendered_yaml_file_location
|
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)
|
5
keywords/openstack/command_wrappers.py
Normal file
5
keywords/openstack/command_wrappers.py
Normal 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}'"
|
29
keywords/openstack/openstack/openstack_json_parser.py
Normal file
29
keywords/openstack/openstack/openstack_json_parser.py
Normal 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)
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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": ""
|
||||||
|
})
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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"
|
@@ -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
|
@@ -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)
|
@@ -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
|
@@ -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
|
@@ -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)
|
||||||
|
|
@@ -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
|
@@ -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
|
45
resources/openstack/stack/template/flavor.yaml
Normal file
45
resources/openstack/stack/template/flavor.yaml
Normal 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 }
|
119
resources/openstack/stack/template/network.yaml
Normal file
119
resources/openstack/stack/template/network.yaml
Normal 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 }
|
62
resources/openstack/stack/template/network_segment.yaml
Normal file
62
resources/openstack/stack/template/network_segment.yaml
Normal 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 }
|
90
resources/openstack/stack/template/project.yaml
Normal file
90
resources/openstack/stack/template/project.yaml
Normal 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] }
|
61
resources/openstack/stack/template/qos.yaml
Normal file
61
resources/openstack/stack/template/qos.yaml
Normal 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 }
|
34
resources/openstack/stack/template/role_assignment.yaml
Normal file
34
resources/openstack/stack/template/role_assignment.yaml
Normal 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 }
|
39
resources/openstack/stack/template/user.yaml
Normal file
39
resources/openstack/stack/template/user.yaml
Normal 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 }
|
57
resources/openstack/stack/template/webimage.yaml
Normal file
57
resources/openstack/stack/template/webimage.yaml
Normal 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] }
|
52
testcases/openstack/openstack_stack.py
Normal file
52
testcases/openstack/openstack_stack.py
Normal 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)
|
Reference in New Issue
Block a user