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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions tests/scripts/helpers/kruize.py
Original file line number Diff line number Diff line change
Expand Up @@ -692,3 +692,85 @@ def update_metadata_profile(metadata_profile_json_file, name=None, **kwargs):
print("Response status code = ", response.status_code)
print(response.json())
return response


# Description: This function creates a layer using createLayer API
# Input Parameters: layer input json file
def create_layer(layer_json_file):
json_file = open(layer_json_file, "r")
layer_json = json.loads(json_file.read())

print("\nCreating layer...")
print("\n************************************************************")
pretty_json_str = json.dumps(layer_json, indent=4)
print(pretty_json_str)
print("\n************************************************************")

url = URL + "/createLayer"
print("URL = ", url)

response = requests.post(url, json=layer_json)
print("Response status code = ", response.status_code)
print(response.text)
return response


# Description: This function lists layers from Kruize Autotune using GET listLayers API
# Input Parameters: layer name (optional)
def list_layers(layer_name=None, logging=True):
print("\nListing the layers...")

query_params = {}

if layer_name is not None:
query_params['name'] = layer_name

query_string = "&".join(f"{key}={value}" for key, value in query_params.items())

url = URL + "/listLayers"
if query_string:
url += "?" + query_string
print("URL = ", url)
print("PARAMS = ", query_params)
response = requests.get(url)

print("Response status code = ", response.status_code)
if logging:
print("\n************************************************************")
print(response.text)
print("\n************************************************************")
return response


# Description: This function deletes a layer from the database (temporary workaround until deleteLayer API is implemented)
# Input Parameters: layer name
def delete_layer_from_db(layer_name):
"""
Helper function to delete a layer from the database.
This is a temporary workaround until deleteLayer API is implemented.

Args:
layer_name: Name of the layer to delete

Returns:
bool: True if deletion succeeded, False otherwise
"""
import subprocess
try:
cmd = [
"kubectl", "exec", "-n", "monitoring",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace is hard coded to monitoring, will not work with openshift

"deployment/kruize-db-deployment", "--",
"psql", "-U", "admin", "-d", "kruizeDB",
"-c", f"DELETE FROM kruize_lm_layer WHERE layer_name='{layer_name}';"
]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode == 0:
print(f" ✓ Cleaned up layer '{layer_name}' from database")
return True
else:
# Don't fail - just warn
print(f" ⚠ Warning: Could not clean up layer '{layer_name}': {result.stderr}")
return False
except Exception as e:
print(f" ⚠ Warning: Error cleaning up layer '{layer_name}': {e}")
return False
27 changes: 27 additions & 0 deletions tests/scripts/helpers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,24 @@
DATASOURCE_NOT_SERVICEABLE = "Datasource %s is not serviceable."
RUNTIMES_RECOMMENDATIONS_NOT_AVAILABLE = "Runtimes recommendations are unavailable for the provided datasource."

# Layer API Messages
CREATE_LAYER_SUCCESS_MSG = "Layer : %s created successfully. View Layers at /listLayers"
LAYER_DUPLICATE_MSG = "Layer with name '%s' already exists"
LAYER_NOT_FOUND_MSG = "Layer with name '%s' not found"
LAYER_METADATA_NAME_NULL_MSG = "metadata.name cannot be null or empty"
LAYER_NAME_NULL_MSG = "layer_name cannot be null or empty"
LAYER_PRESENCE_NULL_MSG = "layer_presence cannot be null"
LAYER_TUNABLES_NULL_OR_EMPTY_MSG = "tunables cannot be null or empty - layer must have at least one tunable"
LAYER_LEVEL_NEGATIVE_MSG = "layer_level cannot be negative"
LAYER_PRESENCE_MISSING_MSG = "layer_presence configuration missing: must specify exactly one of: presence='always', queries, or label"
LAYER_PRESENCE_MULTIPLE_TYPES_MSG = "layer_presence cannot specify multiple types. Choose exactly one: presence, queries, or label"
LAYER_DUPLICATE_TUNABLE_NAMES_MSG = "Layer contains duplicate tunable names"
TUNABLE_MIXED_CONFIG_MSG = "Tunable '%s' cannot have both categorical choices and numeric bounds/step configured"
TUNABLE_MISSING_CONFIG_MSG = "Tunable '%s' must have either categorical choices or numeric bounds/step configured"
TUNABLE_INVALID_BOUNDS_MSG = "Tunable '%s' has invalid bounds"
TUNABLE_INVALID_STEP_MSG = "Tunable '%s' has invalid step"
TUNABLE_EMPTY_CHOICES_MSG = "Tunable '%s' is categorical but has null or empty choices list"


# Kruize Recommendations Notification codes
NOTIFICATION_CODE_FOR_RECOMMENDATIONS_AVAILABLE = "111000"
Expand Down Expand Up @@ -1881,6 +1899,15 @@ def get_metric_profile_dir():

return metric_profile_dir

def get_layer_dir():
# Get the current directory
current_directory = Path(__file__).resolve().parent
# Navigate up 3 levels and build the path to the 'manifests/autotune/layers' directory
base_dir = current_directory.parents[2] # (index 2 because it's zero-based)
layer_dir = base_dir / 'manifests' / 'autotune' / 'layers'

return layer_dir

def validate_local_monitoring_recommendation_data_present(recommendations_json):
if recommendations_json[0]['experiment_type'] == NAMESPACE_EXPERIMENT_TYPE:
assert recommendations_json[0]['kubernetes_objects'][0]['namespaces']['recommendations']['data'], "Recommendations data is expected, but not present."
Expand Down
66 changes: 66 additions & 0 deletions tests/scripts/local_monitoring_tests/Local_monitoring_tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,72 @@ Here are the test scenarios:
- Multiple delete attempts: Delete same metadata profile multiple times and validate the expected error message, ensuring redundant delete requests are handled gracefully.
- Delete metadata profile, try to import cluster metadata and validate the expected error message. This testcase ensures that the cluster metadata cannot be imported once the MetadataProfile is deleted.

### **Create Layer API tests**

Here are the test scenarios:

#### Positive Test Scenarios:
- Create layer with different tunable types:
- Bounded tunables (lower_bound, upper_bound, step)
- Categorical tunables (choices list)
- Mixed tunables (combination of both)
- Create layer with different layer_presence configurations:
- presence='always' (always applicable)
- queries (query-based detection using Prometheus/datasource queries)
- label (label-based detection using Kubernetes labels)
- Create layer with minimum required fields

#### Negative Test Scenarios:

**A. Mandatory Fields Missing/NULL/Empty**
- Create layer with null metadata.name
- Create layer with empty metadata.name
- Create layer with null layer_name
- Create layer with empty layer_name
- Create layer with null layer_presence
- Create layer with null tunables
- Create layer with empty tunables array

**B. Invalid/Negative/Duplicate Values**
- Create layer with negative layer_level value
- Create layer with duplicate tunable names
- Create layer with duplicate layer name (attempting to create same layer twice)

**C. Wrong layer_presence Combinations**
- Create layer with empty layer_presence (no type specified)
- Create layer with both presence AND queries specified
- Create layer with both presence AND label specified
- Create layer with both queries AND label specified
- Create layer with all three types specified (presence, queries, label)

**D. Tunable Bounds/Step Validation**
- Create layer with tunable having null upper_bound
- Create layer with tunable having null lower_bound
- Create layer with tunable having non-numeric upper_bound
- Create layer with tunable having non-numeric lower_bound
- Create layer with tunable having null step
- Create layer with tunable having zero step
- Create layer with tunable having negative step
- Create layer with tunable having negative upper_bound
- Create layer with tunable having negative lower_bound
- Create layer with tunable where lower_bound >= upper_bound
- Create layer with tunable where step > (upper_bound - lower_bound)

**E. Categorical Tunable Validation**
- Create layer with categorical tunable having null choices
- Create layer with categorical tunable having empty choices array
- Create layer with categorical tunable having both choices and bounds (mixed configuration)

### **List Layers API tests**

Here are the test scenarios:

- List all layers without specifying any query parameters
- List specific layer by name using query parameter
- List layers with invalid layer name (non-existing layer)
- List layers before creating any layers
- Validate layer response structure and field values

### **Create Experiment API tests**

Here are the test scenarios:
Expand Down
1 change: 1 addition & 0 deletions tests/scripts/local_monitoring_tests/pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ markers =
extended: mark test as a extended test
test_e2e_pr_check: mark test as a PR check test
test_bulk_api_ros: mark test as a bulk_api ros test
layers: mark test as a layer-related test (createLayer, listLayers)
Loading