Files
zeekay 46c6241c52 Spell every o11y caller /v1/o11y
The server moved off /api/vN; the integration suite, handler tests,
install.sh's health wait and the docs still called the old tree.

Claude-Session: https://claude.ai/code/session_01TUxvfuta1Z44KVNYuAz71F
2026-09-24 15:05:09 -07:00

228 lines
7.4 KiB
Python

import platform
import time
from http import HTTPStatus
from os import path
import docker
import docker.errors
import pytest
import requests
from testcontainers.core.container import DockerContainer, Network
from testcontainers.core.image import DockerImage
from fixtures import reuse, types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def create_o11y(
network: Network,
zeus: types.TestContainerDocker,
gateway: types.TestContainerDocker,
sqlstore: types.TestContainerSQL,
datastore: types.TestContainerDatastore,
request: pytest.FixtureRequest,
pytestconfig: pytest.Config,
cache_key: str = "o11y",
env_overrides: dict | None = None,
) -> types.O11y:
"""
Factory function for creating a O11y container.
Accepts optional env_overrides to customize the container environment.
"""
def create() -> types.O11y:
# Run the migrations for clickhouse
request.getfixturevalue("migrator")
# Get the no-web flag
with_web = pytestconfig.getoption("--with-web")
arch = platform.machine()
if arch == "x86_64":
arch = "amd64"
# Build the image
dockerfile_path = "cmd/enterprise/Dockerfile.integration"
if with_web:
dockerfile_path = "cmd/enterprise/Dockerfile.with-web.integration"
# Docker build context is the repo root — one up from pytest's
# rootdir (tests/).
self = DockerImage(
path=str(pytestconfig.rootpath.parent),
dockerfile_path=dockerfile_path,
tag="o11y:integration",
buildargs={
"TARGETARCH": arch,
"ZEUSURL": zeus.container_configs["8080"].base(),
},
)
self.build()
env = (
{
"O11Y_WEB_ENABLED": False,
"O11Y_WEB_DIRECTORY": "/root/web",
"O11Y_INSTRUMENTATION_LOGS_LEVEL": "debug",
"O11Y_PROMETHEUS_ACTIVE__QUERY__TRACKER_ENABLED": False,
"O11Y_GATEWAY_URL": gateway.container_configs["8080"].base(),
"O11Y_TOKENIZER_JWT_SECRET": "secret",
"O11Y_GLOBAL_INGESTION__URL": "https://ingest.test.o11y.cloud",
"O11Y_USER_PASSWORD_RESET_ALLOW__SELF": True,
"O11Y_USER_PASSWORD_RESET_MAX__TOKEN__LIFETIME": "6h",
"RULES_EVAL_DELAY": "0s",
"O11Y_ALERTMANAGER_O11Y_POLL__INTERVAL": "5s",
"O11Y_ALERTMANAGER_O11Y_ROUTE_GROUP__WAIT": "1s",
"O11Y_ALERTMANAGER_O11Y_ROUTE_GROUP__INTERVAL": "5s",
"O11Y_CLOUDINTEGRATION_AGENT_VERSION": "v0.0.8",
}
| sqlstore.env
| datastore.env
)
if with_web:
env["O11Y_WEB_ENABLED"] = True
if env_overrides:
env = env | env_overrides
container = DockerContainer("o11y:integration")
for k, v in env.items():
container.with_env(k, v)
container.with_exposed_ports(8080)
container.with_network(network=network)
provider = request.config.getoption("--sqlstore-provider")
if provider == "sqlite":
dir_path = path.dirname(sqlstore.env["O11Y_SQLSTORE_SQLITE_PATH"])
container.with_volume_mapping(
dir_path,
dir_path,
"rw",
)
container.start()
def ready(container: DockerContainer) -> None:
for attempt in range(10):
try:
response = requests.get(
f"http://{container.get_container_host_ip()}:{container.get_exposed_port(8080)}/v1/o11y/healthz",
timeout=2,
)
if response.status_code == HTTPStatus.OK:
return
if response.status_code == HTTPStatus.SERVICE_UNAVAILABLE:
logger.error(
"Attempt %s: O11y container %s not ready yet:\n%s",
attempt + 1,
container,
response.text,
)
except Exception as e: # pylint: disable=broad-exception-caught
logger.error(
"Attempt %s at readiness check for O11y container %s failed: %s",
attempt + 1,
container,
e,
)
time.sleep(2)
raise TimeoutError("timeout exceeded while waiting")
try:
ready(container=container)
except Exception as e: # pylint: disable=broad-exception-caught
raise e
return types.O11y(
self=types.TestContainerDocker(
id=container.get_wrapped_container().id,
host_configs={
"8080": types.TestContainerUrlConfig(
"http",
container.get_container_host_ip(),
container.get_exposed_port(8080),
)
},
container_configs={
"8080": types.TestContainerUrlConfig(
"http",
container.get_wrapped_container().name,
8080,
)
},
),
sqlstore=sqlstore,
telemetrystore=datastore,
zeus=zeus,
gateway=gateway,
)
def delete(container: types.O11y) -> None:
client = docker.from_env()
try:
client.containers.get(container_id=container.self.id).stop()
client.containers.get(container_id=container.self.id).remove(v=True)
except docker.errors.NotFound:
logger.info(
"Skipping removal of O11y, O11y(%s) not found. Maybe it was manually removed?",
{"id": container.self.id},
)
def restore(cache: dict) -> types.O11y:
self = types.TestContainerDocker.from_cache(cache)
return types.O11y(
self=self,
sqlstore=sqlstore,
telemetrystore=datastore,
zeus=zeus,
gateway=gateway,
)
return reuse.wrap(
request,
pytestconfig,
cache_key,
empty=lambda: types.O11y(
self=types.TestContainerDocker(
id="",
host_configs={},
container_configs={},
),
sqlstore=sqlstore,
telemetrystore=datastore,
zeus=zeus,
gateway=gateway,
),
create=create,
delete=delete,
restore=restore,
)
@pytest.fixture(name="o11y", scope="package")
def o11y( # pylint: disable=too-many-arguments,too-many-positional-arguments
network: Network,
zeus: types.TestContainerDocker,
gateway: types.TestContainerDocker,
sqlstore: types.TestContainerSQL,
datastore: types.TestContainerDatastore,
request: pytest.FixtureRequest,
pytestconfig: pytest.Config,
) -> types.O11y:
"""
Package-scoped fixture for setting up O11y.
"""
return create_o11y(
network=network,
zeus=zeus,
gateway=gateway,
sqlstore=sqlstore,
datastore=datastore,
request=request,
pytestconfig=pytestconfig,
)