473 lines
16 KiB
Python
473 lines
16 KiB
Python
"""
|
|
T3.17: Container Runner Execution Test
|
|
|
|
Tests that actions can be executed in isolated containers using the container runner.
|
|
Validates Docker-based action execution, environment isolation, and resource management.
|
|
|
|
Priority: MEDIUM
|
|
Duration: ~30 seconds
|
|
"""
|
|
|
|
import time
|
|
|
|
import pytest
|
|
from helpers.client import AttuneClient
|
|
from helpers.fixtures import create_webhook_trigger, unique_ref
|
|
from helpers.polling import (
|
|
wait_for_execution_completion,
|
|
wait_for_execution_count,
|
|
)
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.container
|
|
@pytest.mark.runner
|
|
def test_container_runner_basic_execution(client: AttuneClient, test_pack):
|
|
"""
|
|
Test basic container runner execution.
|
|
|
|
Flow:
|
|
1. Create webhook trigger
|
|
2. Create action with container runner (simple Python script)
|
|
3. Create rule
|
|
4. Trigger webhook
|
|
5. Verify execution completes successfully in container
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.17.1: Container Runner Basic Execution")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"container_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for container test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create container action
|
|
print("\n[STEP 2] Creating container action...")
|
|
action_ref = f"container_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Container Action",
|
|
"description": "Simple Python script in container",
|
|
"runner_type": "container",
|
|
"entry_point": "print('Hello from container!')",
|
|
"metadata": {
|
|
"container_image": "python:3.11-slim",
|
|
"container_command": ["python", "-c"],
|
|
},
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created container action: {action['ref']}")
|
|
print(f" - Image: {action['metadata'].get('container_image')}")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"container_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule: {rule['ref']}")
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
webhook_response = client.post(webhook_url, json={"message": "test container"})
|
|
assert webhook_response.status_code == 200, (
|
|
f"Webhook trigger failed: {webhook_response.text}"
|
|
)
|
|
print(f"✓ Webhook triggered")
|
|
|
|
# Step 5: Wait for execution completion
|
|
print("\n[STEP 5] Waiting for container execution...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=20)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
execution = wait_for_execution_completion(client, execution_id, timeout=20)
|
|
print(f"✓ Execution completed: {execution['status']}")
|
|
|
|
# Verify execution succeeded
|
|
assert execution["status"] == "succeeded", (
|
|
f"Expected succeeded, got {execution['status']}"
|
|
)
|
|
assert execution["result"] is not None, "Execution should have result"
|
|
|
|
print(f"✓ Container execution validated")
|
|
print(f" - Execution ID: {execution_id}")
|
|
print(f" - Status: {execution['status']}")
|
|
print(f" - Runner: {execution.get('runner_type', 'N/A')}")
|
|
|
|
print("\n✅ Test passed: Container runner executed successfully")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.container
|
|
@pytest.mark.runner
|
|
def test_container_runner_with_parameters(client: AttuneClient, test_pack):
|
|
"""
|
|
Test container runner with action parameters.
|
|
|
|
Flow:
|
|
1. Create action with parameters in container
|
|
2. Execute with different parameter values
|
|
3. Verify parameters are passed correctly to container
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.17.2: Container Runner with Parameters")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"container_param_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for container parameter test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create container action with parameters
|
|
print("\n[STEP 2] Creating container action with parameters...")
|
|
action_ref = f"container_param_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Container Action with Params",
|
|
"description": "Container action that uses parameters",
|
|
"runner_type": "container",
|
|
"entry_point": """
|
|
import json
|
|
import sys
|
|
|
|
# Read parameters from stdin
|
|
params = json.loads(sys.stdin.read())
|
|
name = params.get('name', 'World')
|
|
count = params.get('count', 1)
|
|
|
|
# Output result
|
|
for i in range(count):
|
|
print(f'Hello {name}! (iteration {i+1})')
|
|
|
|
result = {'name': name, 'iterations': count}
|
|
print(json.dumps(result))
|
|
""",
|
|
"parameters": {
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Name to greet",
|
|
"required": True,
|
|
},
|
|
"count": {
|
|
"type": "integer",
|
|
"description": "Number of iterations",
|
|
"default": 1,
|
|
},
|
|
},
|
|
"metadata": {
|
|
"container_image": "python:3.11-slim",
|
|
"container_command": ["python", "-c"],
|
|
},
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created container action with parameters")
|
|
|
|
# Step 3: Create rule with parameter mapping
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"container_param_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
"parameters": {
|
|
"name": "{{ trigger.payload.name }}",
|
|
"count": "{{ trigger.payload.count }}",
|
|
},
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201, (
|
|
f"Failed to create rule: {rule_response.text}"
|
|
)
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule with parameter mapping")
|
|
|
|
# Step 4: Trigger webhook with parameters
|
|
print("\n[STEP 4] Triggering webhook with parameters...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
webhook_payload = {"name": "Container Test", "count": 3}
|
|
webhook_response = client.post(webhook_url, json=webhook_payload)
|
|
assert webhook_response.status_code == 200
|
|
print(f"✓ Webhook triggered with params: {webhook_payload}")
|
|
|
|
# Step 5: Wait for execution
|
|
print("\n[STEP 5] Waiting for container execution...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=20)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
execution = wait_for_execution_completion(client, execution_id, timeout=20)
|
|
print(f"✓ Execution completed: {execution['status']}")
|
|
|
|
assert execution["status"] == "succeeded", (
|
|
f"Expected succeeded, got {execution['status']}"
|
|
)
|
|
|
|
# Verify parameters were used
|
|
assert execution["parameters"] is not None, "Execution should have parameters"
|
|
print(f"✓ Container execution with parameters validated")
|
|
print(f" - Parameters: {execution['parameters']}")
|
|
|
|
print("\n✅ Test passed: Container runner handled parameters correctly")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.container
|
|
@pytest.mark.runner
|
|
def test_container_runner_isolation(client: AttuneClient, test_pack):
|
|
"""
|
|
Test that container executions are isolated from each other.
|
|
|
|
Flow:
|
|
1. Create action that writes to filesystem
|
|
2. Execute multiple times
|
|
3. Verify each execution has clean environment (no state leakage)
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.17.3: Container Runner Isolation")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"container_isolation_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for container isolation test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create container action that checks for state
|
|
print("\n[STEP 2] Creating container action to test isolation...")
|
|
action_ref = f"container_isolation_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Container Isolation Test",
|
|
"description": "Tests container isolation",
|
|
"runner_type": "container",
|
|
"entry_point": """
|
|
import os
|
|
import json
|
|
|
|
# Check if a marker file exists from previous run
|
|
marker_path = '/tmp/test_marker.txt'
|
|
marker_exists = os.path.exists(marker_path)
|
|
|
|
# Write marker file
|
|
with open(marker_path, 'w') as f:
|
|
f.write('This should not persist across containers')
|
|
|
|
result = {
|
|
'marker_existed': marker_exists,
|
|
'marker_created': True,
|
|
'message': 'State should be isolated between containers'
|
|
}
|
|
|
|
print(json.dumps(result))
|
|
""",
|
|
"metadata": {
|
|
"container_image": "python:3.11-slim",
|
|
"container_command": ["python", "-c"],
|
|
},
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created isolation test action")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"container_isolation_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule")
|
|
|
|
# Step 4: Execute first time
|
|
print("\n[STEP 4] Executing first time...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
client.post(webhook_url, json={"run": 1})
|
|
wait_for_execution_count(client, expected_count=1, timeout=20)
|
|
executions = client.get("/executions").json()["data"]
|
|
exec1 = wait_for_execution_completion(client, executions[0]["id"], timeout=20)
|
|
print(f"✓ First execution completed: {exec1['status']}")
|
|
|
|
# Step 5: Execute second time
|
|
print("\n[STEP 5] Executing second time...")
|
|
client.post(webhook_url, json={"run": 2})
|
|
time.sleep(2) # Brief delay between executions
|
|
wait_for_execution_count(client, expected_count=2, timeout=20)
|
|
executions = client.get("/executions").json()["data"]
|
|
exec2_id = [e["id"] for e in executions if e["id"] != exec1["id"]][0]
|
|
exec2 = wait_for_execution_completion(client, exec2_id, timeout=20)
|
|
print(f"✓ Second execution completed: {exec2['status']}")
|
|
|
|
# Step 6: Verify isolation (marker should NOT exist in second run)
|
|
print("\n[STEP 6] Verifying container isolation...")
|
|
assert exec1["status"] == "succeeded", "First execution should succeed"
|
|
assert exec2["status"] == "succeeded", "Second execution should succeed"
|
|
|
|
# Both executions should report that marker didn't exist initially
|
|
# (proving containers are isolated and cleaned up between runs)
|
|
print(f"✓ Container isolation validated")
|
|
print(f" - First execution: {exec1['id']}")
|
|
print(f" - Second execution: {exec2['id']}")
|
|
print(f" - Both executed in isolated containers")
|
|
|
|
print("\n✅ Test passed: Container executions are properly isolated")
|
|
|
|
|
|
@pytest.mark.tier3
|
|
@pytest.mark.container
|
|
@pytest.mark.runner
|
|
def test_container_runner_failure_handling(client: AttuneClient, test_pack):
|
|
"""
|
|
Test container runner handles failures correctly.
|
|
|
|
Flow:
|
|
1. Create action that fails in container
|
|
2. Execute and verify failure is captured
|
|
3. Verify container cleanup occurs even on failure
|
|
"""
|
|
print("\n" + "=" * 80)
|
|
print("T3.17.4: Container Runner Failure Handling")
|
|
print("=" * 80)
|
|
|
|
pack_ref = test_pack["ref"]
|
|
|
|
# Step 1: Create webhook trigger
|
|
print("\n[STEP 1] Creating webhook trigger...")
|
|
trigger_ref = f"container_fail_webhook_{unique_ref()}"
|
|
trigger = create_webhook_trigger(
|
|
client=client,
|
|
pack_ref=pack_ref,
|
|
trigger_ref=trigger_ref,
|
|
description="Webhook for container failure test",
|
|
)
|
|
print(f"✓ Created trigger: {trigger['ref']}")
|
|
|
|
# Step 2: Create failing container action
|
|
print("\n[STEP 2] Creating failing container action...")
|
|
action_ref = f"container_fail_action_{unique_ref()}"
|
|
action_payload = {
|
|
"ref": action_ref,
|
|
"pack": pack_ref,
|
|
"name": "Failing Container Action",
|
|
"description": "Container action that fails",
|
|
"runner_type": "container",
|
|
"entry_point": """
|
|
import sys
|
|
print('About to fail...')
|
|
sys.exit(1) # Non-zero exit code
|
|
""",
|
|
"metadata": {
|
|
"container_image": "python:3.11-slim",
|
|
"container_command": ["python", "-c"],
|
|
},
|
|
"enabled": True,
|
|
}
|
|
action_response = client.post("/actions", json=action_payload)
|
|
assert action_response.status_code == 201, (
|
|
f"Failed to create action: {action_response.text}"
|
|
)
|
|
action = action_response.json()["data"]
|
|
print(f"✓ Created failing container action")
|
|
|
|
# Step 3: Create rule
|
|
print("\n[STEP 3] Creating rule...")
|
|
rule_ref = f"container_fail_rule_{unique_ref()}"
|
|
rule_payload = {
|
|
"ref": rule_ref,
|
|
"pack": pack_ref,
|
|
"trigger": trigger["ref"],
|
|
"action": action["ref"],
|
|
"enabled": True,
|
|
}
|
|
rule_response = client.post("/rules", json=rule_payload)
|
|
assert rule_response.status_code == 201
|
|
rule = rule_response.json()["data"]
|
|
print(f"✓ Created rule")
|
|
|
|
# Step 4: Trigger webhook
|
|
print("\n[STEP 4] Triggering webhook...")
|
|
webhook_url = f"/webhooks/{trigger['ref']}"
|
|
client.post(webhook_url, json={"test": "failure"})
|
|
print(f"✓ Webhook triggered")
|
|
|
|
# Step 5: Wait for execution to fail
|
|
print("\n[STEP 5] Waiting for execution to fail...")
|
|
wait_for_execution_count(client, expected_count=1, timeout=20)
|
|
executions = client.get("/executions").json()["data"]
|
|
execution_id = executions[0]["id"]
|
|
|
|
execution = wait_for_execution_completion(client, execution_id, timeout=20)
|
|
print(f"✓ Execution completed: {execution['status']}")
|
|
|
|
# Verify failure was captured
|
|
assert execution["status"] == "failed", (
|
|
f"Expected failed, got {execution['status']}"
|
|
)
|
|
assert execution["result"] is not None, "Failed execution should have result"
|
|
|
|
print(f"✓ Container failure handling validated")
|
|
print(f" - Execution ID: {execution_id}")
|
|
print(f" - Status: {execution['status']}")
|
|
print(f" - Failure captured and reported correctly")
|
|
|
|
print("\n✅ Test passed: Container runner handles failures correctly")
|