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)
|
||||
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)
|
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