Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

e2etests #717

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
41 changes: 41 additions & 0 deletions e2e-tests/.env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# OPAL Test Environment Configuration

# Server Configuration
SERVER_PORT=7002
SERVER_HOST=0.0.0.0
SERVER_WORKERS=4
SERVER_LOG_LEVEL=DEBUG
SERVER_MASTER_TOKEN=master-token-for-testing

# Client Configuration
CLIENT_PORT=7000
CLIENT_HOST=0.0.0.0
CLIENT_TOKEN=default-token-for-testing
CLIENT_LOG_LEVEL=DEBUG

# Database Configuration
POSTGRES_PORT=5432
POSTGRES_HOST=broadcast_channel
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

# Policy Configuration
POLICY_REPO_URL=https://github.com/permitio/opal-example-policy-repo
POLICY_REPO_POLLING_INTERVAL=30

# Network Configuration
NETWORK_NAME=opal_test_network

# Authentication Configuration
AUTH_JWT_AUDIENCE=https://api.opal.ac/v1/
AUTH_JWT_ISSUER=https://opal.ac/

# Test Configuration
TEST_TIMEOUT=300
TEST_RETRY_INTERVAL=2
TEST_MAX_RETRIES=30

# Statistics Configuration
STATISTICS_ENABLED=false
STATISTICS_CHECK_TIMEOUT=10
83 changes: 83 additions & 0 deletions e2e-tests/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from pydantic import BaseSettings, Field
from typing import Optional
from pathlib import Path
import os

class OPALEnvironment(BaseSettings):
"""Environment configuration for OPAL tests with support for .env file"""
# Server Configuration
SERVER_PORT: int = Field(7002, description="OPAL server port")
SERVER_HOST: str = Field("0.0.0.0", description="OPAL server host")
SERVER_WORKERS: int = Field(4, description="Number of server workers")
SERVER_LOG_LEVEL: str = Field("DEBUG", description="Server log level")
SERVER_MASTER_TOKEN: str = Field("master-token-for-testing", description="Server master token")

# Client Configuration
CLIENT_PORT: int = Field(7000, description="OPAL client port")
CLIENT_HOST: str = Field("0.0.0.0", description="OPAL client host")
CLIENT_TOKEN: str = Field("default-token-for-testing", description="Client auth token")
CLIENT_LOG_LEVEL: str = Field("DEBUG", description="Client log level")

# Database Configuration
POSTGRES_PORT: int = Field(5432, description="PostgreSQL port")
POSTGRES_HOST: str = Field("broadcast_channel", description="PostgreSQL host")
POSTGRES_DB: str = Field("postgres", description="PostgreSQL database")
POSTGRES_USER: str = Field("postgres", description="PostgreSQL user")
POSTGRES_PASSWORD: str = Field("postgres", description="PostgreSQL password")


# Statistics Configuration
STATISTICS_ENABLED: bool = Field(True, description="Enable statistics collection")
STATISTICS_CHECK_TIMEOUT: int = Field(10, description="Timeout for statistics checks in seconds")

# Policy Configuration
POLICY_REPO_URL: str = Field(
"https://github.com/permitio/opal-example-policy-repo",
description="Git repository URL for policies"
)
POLICY_REPO_POLLING_INTERVAL: int = Field(30, description="Policy repo polling interval in seconds")

# Network Configuration
NETWORK_NAME: str = Field("opal_test_network", description="Docker network name")

# Authentication Configuration
AUTH_JWT_AUDIENCE: str = Field("https://api.opal.ac/v1/", description="JWT audience")
AUTH_JWT_ISSUER: str = Field("https://opal.ac/", description="JWT issuer")

# Test Configuration
TEST_TIMEOUT: int = Field(300, description="Test timeout in seconds")
TEST_RETRY_INTERVAL: int = Field(2, description="Retry interval in seconds")
TEST_MAX_RETRIES: int = Field(30, description="Maximum number of retries")

class Config:
env_file = '.env'
env_file_encoding = 'utf-8'
case_sensitive = True

@property
def postgres_dsn(self) -> str:
"""Get PostgreSQL connection string"""
return f"postgres://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}"

@classmethod
def load_from_env_file(cls, env_file: str = '.env') -> 'OPALEnvironment':
"""Load configuration from specific env file"""
if not os.path.exists(env_file):
raise FileNotFoundError(f"Environment file not found: {env_file}")

return cls(_env_file=env_file)

def get_environment() -> OPALEnvironment:
"""Get environment configuration, with support for local development overrides"""
# Try local dev config first
local_env = Path('.env.local')
if local_env.exists():
return OPALEnvironment.load_from_env_file('.env.local')

# Fallback to default .env
default_env = Path('.env')
if default_env.exists():
return OPALEnvironment.load_from_env_file('.env')

# Use defaults/environment variables
return OPALEnvironment()
75 changes: 75 additions & 0 deletions e2e-tests/container_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Container configuration helpers"""
import json
from testcontainers.core.container import DockerContainer
from config import OPALEnvironment

def configure_postgres(container: DockerContainer, config: OPALEnvironment):
"""Configure Postgres container"""
container.with_env("POSTGRES_DB", config.POSTGRES_DB)
container.with_env("POSTGRES_USER", config.POSTGRES_USER)
container.with_env("POSTGRES_PASSWORD", config.POSTGRES_PASSWORD)
container.with_exposed_ports(config.POSTGRES_PORT)
container.with_kwargs(network=config.NETWORK_NAME)

def configure_server(container: DockerContainer, config: OPALEnvironment):
"""Configure OPAL server container with all required environment variables"""
env_vars = {
"PORT": str(config.SERVER_PORT),
"HOST": config.SERVER_HOST,
"UVICORN_NUM_WORKERS": str(config.SERVER_WORKERS),
"LOG_LEVEL": config.SERVER_LOG_LEVEL,
"OPAL_STATISTICS_ENABLED": "true",
"OPAL_BROADCAST_URI": config.postgres_dsn,
"BROADCAST_CHANNEL_NAME": "opal_updates",
"OPAL_POLICY_REPO_URL": config.POLICY_REPO_URL,
"OPAL_POLICY_REPO_POLLING_INTERVAL": str(config.POLICY_REPO_POLLING_INTERVAL),
"OPAL_DATA_CONFIG_SOURCES": json.dumps({
"config": {
"entries": [{
"url": "http://opal_server:7002/policy-data",
"topics": ["policy_data"],
"dst_path": "/static"
}]
}
}),
"AUTH_JWT_AUDIENCE": config.AUTH_JWT_AUDIENCE,
"AUTH_JWT_ISSUER": config.AUTH_JWT_ISSUER,
"AUTH_MASTER_TOKEN": config.SERVER_MASTER_TOKEN,
"OPAL_LOG_FORMAT_INCLUDE_PID": "true",
"LOG_FORMAT": "text",
"LOG_TRACEBACK": "true"
}

for key, value in env_vars.items():
container.with_env(key, value)

container.with_exposed_ports(config.SERVER_PORT)
container.with_kwargs(network=config.NETWORK_NAME)

def configure_client(container: DockerContainer, config: OPALEnvironment):
"""Configure OPAL client container with all required environment variables"""
env_vars = {
"OPAL_SERVER_URL": f"http://opal_server:{config.SERVER_PORT}",
"PORT": str(config.CLIENT_PORT),
"HOST": config.CLIENT_HOST,
"LOG_LEVEL": config.CLIENT_LOG_LEVEL,
"OPAL_CLIENT_TOKEN": config.CLIENT_TOKEN,
"AUTH_JWT_AUDIENCE": config.AUTH_JWT_AUDIENCE,
"AUTH_JWT_ISSUER": config.AUTH_JWT_ISSUER,
"POLICY_UPDATER_ENABLED": "true",
"DATA_UPDATER_ENABLED": "true",
"INLINE_OPA_ENABLED": "true",
"OPA_HEALTH_CHECK_POLICY_ENABLED": "true",
"OPAL_INLINE_OPA_LOG_FORMAT": "http",
"INLINE_OPA_CONFIG": "{}",
"OPAL_STATISTICS_ENABLED": "true",
"OPAL_LOG_FORMAT_INCLUDE_PID": "true",
"LOG_FORMAT": "text",
"LOG_TRACEBACK": "true"
}

for key, value in env_vars.items():
container.with_env(key, value)

container.with_exposed_ports(config.CLIENT_PORT, 8181)
container.with_kwargs(network=config.NETWORK_NAME)
10 changes: 10 additions & 0 deletions e2e-tests/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[pytest]
asyncio_mode = strict
# Set the default fixture loop scope to function
asyncio_default_fixture_loop_scope = function

# Optional but recommended settings
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format = %Y-%m-%d %H:%M:%S
76 changes: 76 additions & 0 deletions e2e-tests/test_cases.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Test cases for OPAL integration tests"""
import logging
import json
import time
import pytest
import requests
from config import OPALEnvironment, get_environment
from test_environment import OPALTestEnvironment

logger = logging.getLogger(__name__)

@pytest.fixture(scope="module")
def opal_config():
"""Fixture that provides environment configuration"""
return get_environment()

@pytest.fixture(scope="module")
def opal_env(opal_config):
"""Main fixture that provides the test environment"""
env = OPALTestEnvironment(opal_config)
env.setup()
yield env
env.teardown()

def test_opal_baseline(opal_env, opal_config):
"""Test basic OPAL functionality"""
logger.info("Starting OPAL Environment Tests")

time.sleep(opal_config.TEST_RETRY_INTERVAL * 5) # Allow services to stabilize

# Test server health
logger.info("Testing Server Health")
server_url = f"http://{opal_env.containers['server'].get_container_host_ip()}:{opal_env.containers['server'].get_exposed_port(opal_config.SERVER_PORT)}"
server_health = requests.get(f"{server_url}/healthcheck")
assert server_health.status_code == 200, "Server health check failed"
logger.info("Server health check passed")

# Test client health
logger.info("Testing Client Health")
client_url = f"http://{opal_env.containers['client'].get_container_host_ip()}:{opal_env.containers['client'].get_exposed_port(opal_config.CLIENT_PORT)}"
client_health = requests.get(f"{client_url}/healthcheck")
assert client_health.status_code == 200, "Client health check failed"
logger.info("Client health check passed")

# Test OPA endpoint
logger.info("Testing OPA Health")
opa_url = f"http://{opal_env.containers['client'].get_container_host_ip()}:{opal_env.containers['client'].get_exposed_port(8181)}"
opa_health = requests.get(f"{opa_url}/health")
assert opa_health.status_code == 200, "OPA health check failed"
logger.info("OPA health check passed")

# Check server statistics
logger.info("Checking Server Statistics")
stats_url = f"{server_url}/statistics"
headers = {"Authorization": f"Bearer {opal_config.SERVER_MASTER_TOKEN}"}
stats_response = requests.get(stats_url, headers=headers)
assert stats_response.status_code == 200, f"Failed to get statistics: HTTP {stats_response.status_code}"
stats_data = stats_response.json()
logger.info("Server Statistics: %s", json.dumps(stats_data, indent=2))

# Check for errors in logs
logger.info("Checking Container Logs")
for name, container in opal_env.containers.items():
logs = container.get_logs()[0].decode()
error_count = sum(1 for line in logs.split('\n') if "ERROR" in line)
critical_count = sum(1 for line in logs.split('\n') if "CRITICAL" in line)

if error_count > 0 or critical_count > 0:
logger.error(f"Found errors in {name} logs:")
logger.error(f"- {error_count} ERRORs")
logger.error(f"- {critical_count} CRITICALs")
assert False, f"Found {error_count + critical_count} errors in {name} logs"
else:
logger.info(f"{name}: No errors found in logs")

logger.info("All basic health checks completed successfully")
Loading
Loading