Matriz E2E por servico Proxmox¶
A suite E2E do proxbox-api executa cada celula em Docker contra um container
mock dedicado de proxmox-sdk. O mock e separado por servico Proxmox
(pve, pbs, pdm) e o CI paraleliza a suite nos tres, garantindo que um
unico push exercita todos os stubs de servico suportados.
Para o desenho dos demais workflows — jobs de CI, modos de dependencia, fases de publicacao escalonadas no TestPyPI/PyPI e variantes de imagem Docker — veja CI and E2E Workflows.
Por que existe o eixo de servico¶
O proxmox-sdk publica tres tags de imagem por servico em
emersonfelipesp/proxmox-sdk:
| Servico | Tag da imagem | Cobertura |
|---|---|---|
pve |
emersonfelipesp/proxmox-sdk:latest-pve |
Superficie completa de OpenAPI do Proxmox VE (646 endpoints). Executa o pipeline historico de sync. |
pbs |
emersonfelipesp/proxmox-sdk:latest-pbs |
Stub do Proxmox Backup Server. Apenas /health (generico) + / (identificador de servico); rotas no formato PVE sao propositalmente ausentes. |
pdm |
emersonfelipesp/proxmox-sdk:latest-pdm |
Stub do Proxmox Datacenter Manager. Hoje tem o mesmo formato do PBS. |
Rodar o mesmo backend, o mesmo container NetBox e as mesmas fixtures contra as tres tags e a maneira mais barata de pegar regressoes que quebrariam uma conexao real com PBS ou PDM — mesmo antes do contrato OpenAPI estar totalmente gerado upstream.
Formato da matriz¶
ci.yml¶
A matriz de CI e gerada dinamicamente pelo job setup. O gerador emite o
produto cartesiano de:
| Eixo | Origem | Padrao |
|---|---|---|
base (combinacao de transporte) |
Lista hard-coded em setup.gen |
7 combinacoes cobrindo http_manage, https_nginx, https_granian e dual-stack IPv6 |
netbox_proxbox_mode |
Input INPUT_NETBOX_PROXBOX_MODE e o tipo de evento |
dev em push/PR, [dev, pypi] em release |
netbox_version |
.github/netbox-versions.json |
Atualmente tres tags NetBox certificadas |
proxmox_service |
Lista hard-coded ["pve", "pbs", "pdm"] |
Tres servicos em todas as execucoes |
O produto cartesiano e portanto 7 (transporte) × 1–2 (modo) × 3 (NetBox) × 3 (servico) = 63–126 celulas.
Cada celula usa fail-fast: false, entao a falha de uma celula nao aborta o
restante do run.
A tag da imagem e injetada no runner a partir da matriz e usada tanto no
docker pull quanto no container proxmox-e2e-mock:
env:
PROXMOX_OPENAPI_IMAGE: emersonfelipesp/proxmox-sdk:latest-${{ matrix.proxmox_service }}
PROXMOX_SERVICE: ${{ matrix.proxmox_service }}
publish-testpypi.yml¶
Os jobs de E2E pre e pos publicacao fixam o mesmo eixo estaticamente com
proxmox_service: [pve, pbs, pdm] para que os artefatos publicados (dist no
TestPyPI, imagem no Docker Hub) sejam validados contra todos os stubs de
servico antes da release ser finalizada.
Arquitetura¶
Toda celula sobe o mesmo layout fisico no runner. A unica diferenca entre
celulas e qual tag de proxmox-sdk esta carregada em proxmox-e2e-mock.
flowchart LR
A[GitHub Actions Runner]
subgraph D[Rede Docker: proxbox-e2e]
NB[Container NetBox<br>netbox-proxbox instalado]
NGINX[nginx HTTPS opcional]
API[Container proxbox-api<br>target raw / nginx / granian]
PM[Mock proxmox-sdk<br>tag latest-pve / latest-pbs / latest-pdm]
PG[(PostgreSQL)]
RD[(Redis)]
end
A --> NB
A --> API
A --> PM
NB --> PG
NB --> RD
API -->|Leituras no formato Proxmox| PM
API -->|Escritas REST no NetBox| NB
NGINX --> NB
Camada de fixtures¶
A camada Python de fixtures vive em
proxbox_api/e2e/fixtures/proxmox_sdk_mock.py e e o unico ponto que decide
"este run e no formato PVE, ou e um stub de servico sem dados de VM/cluster?".
| Helper | Responsabilidade |
|---|---|
_resolve_proxmox_service(service="pve") |
Resolver atento a variaveis. Le PROXMOX_SERVICE quando o chamador nao passou servico explicito, normaliza para minusculas e cai em pve. |
_empty_cluster(name, service) |
Retorna um cluster rotulado com o servico, sem nos/VMs. Usado nas celulas PBS e PDM. |
create_minimal_cluster(prefix, service="pve") |
Um no e duas VMs (QEMU + LXC) em PVE; cluster vazio em servico nao-PVE. |
create_multi_cluster(prefix, service="pve") |
Dois clusters PVE com varios nos/VMs; um unico cluster vazio em servico nao-PVE. |
create_cluster_with_backups(prefix, service="pve") |
Cluster PVE + metadados de backup; em servico nao-PVE retorna cluster vazio e lista de backups vazia. |
create_custom_cluster(name, nodes_spec, vms_spec, prefix, service="pve") |
Topologia PVE customizada; cluster vazio em servico nao-PVE. |
Os testes nao precisam saber qual servico esta carregado — pedem a fixture e a fixture devolve o estado PVE realista ou um cluster vazio rotulado. O que muda entre servicos e a selecao de testes, nao a superficie da fixture.
Politica de skip¶
tests/e2e/conftest.py expoe duas fixtures session-scope que controlam o
roteamento por servico:
@pytest.fixture(scope="session")
def proxmox_service() -> str:
return (os.environ.get("PROXMOX_SERVICE", "pve").strip().lower() or "pve")
@pytest.fixture(scope="session")
def requires_pve_schema(proxmox_service: str) -> None:
if proxmox_service != "pve":
pytest.skip(f"requires PVE schema; service={proxmox_service}")
Modulos de teste que rodam o pipeline completo de sync PVE declaram o requisito de fixture no escopo do modulo:
Hoje isso vale em:
tests/e2e/test_backups_sync.pytests/e2e/test_devices_sync.pytests/e2e/test_vm_sync.py
Quando PROXMOX_SERVICE e pbs ou pdm os modulos inteiros sao pulados
automaticamente, com uma razao visivel nos logs do CI. As fixtures
minimal_cluster / multi_cluster / mock_proxmox_session continuam
montando o cluster vazio rotulado, entao nenhum import de fixture quebra
quando um stub esta carregado.
Smoke de servico¶
Um modulo dedicado verifica que a tag correta esta de fato carregada.
tests/e2e/test_proxmox_mock_health.py roda apenas em celulas PBS e PDM.
Garante que /health responde (probe generico de readiness) e que o payload
da raiz / do mock identifica o stub de servico carregado:
@pytest.mark.asyncio(loop_scope="session")
@pytest.mark.mock_http
async def test_pbs_pdm_mock_root_reports_loaded_service(proxmox_service: str):
if proxmox_service == "pve":
pytest.skip("PBS/PDM service smoke only")
base_url = os.environ.get(
"PROXMOX_MOCK_PUBLISHED_URL", "http://localhost:8006"
).rstrip("/")
async with httpx.AsyncClient(base_url=base_url, timeout=10.0) as client:
health = await client.get("/health")
root = await client.get("/")
assert health.status_code == 200
assert root.status_code == 200
assert proxmox_service in root.text.lower()
Este e o teste que pegaria a tag errada sendo puxada, ou um stub de servico
regredindo o campo service no payload da raiz. (/health e propositalmente
generico em todas as variantes do mock proxmox-sdk.)
Markers pytest e cabos do CI¶
Dois markers controlam a camada E2E em Docker:
mock_http— roda contra o container real doproxmox-sdkna redeproxbox-e2e. Todas as celulas executam esta camada.mock_backend— roda contra oMockBackendin-process. Usado apenas como passada separada.
O workflow do CI roda os dois passos dentro de cada celula:
- name: Run E2E tests (Docker proxmox mock)
env:
PROXMOX_SERVICE: ${{ matrix.proxmox_service }}
run: uv run pytest tests/e2e/ -m "mock_http" --tb=short -v
- name: Run E2E tests with in-process MockBackend
if: github.ref == 'refs/heads/main' && matrix.proxmox_service == 'pve'
env:
PROXMOX_SERVICE: ${{ matrix.proxmox_service }}
run: uv run pytest tests/e2e/ -m "mock_backend" --tb=short -v
O passo mock_backend e protegido por main e por pve. O backend
in-process reusa as fixtures no formato PVE e nao serve para validar o
container de servico em si, entao rodar pbs / pdm ali nao acrescentaria
sinal.
O que cada celula verifica¶
| Verificacao | pve |
pbs |
pdm |
|---|---|---|---|
Prontidao da stack (NetBox API, proxbox-api API, proxmox-sdk /openapi.json) |
sim | sim | sim |
Raiz / do mock reporta o servico carregado |
(smoke pulado) | sim | sim |
Smoke auth/register-key + netbox/endpoint + netbox/status |
sim | sim | sim |
Smoke extras/custom-fields/create |
sim | sim | sim |
test_backups_sync.py (requires_pve_schema) |
sim | skip | skip |
test_devices_sync.py (requires_pve_schema) |
sim | skip | skip |
test_vm_sync.py (requires_pve_schema) |
sim | skip | skip |
Passada mock_backend in-process (apenas main) |
sim | skip | skip |
Celulas PVE seguem certificando o pipeline completo de sync, enquanto celulas PBS e PDM certificam que o plugin, o backend, o NetBox e o mock conseguem coexistir e se alcancar sob aquelas tags de servico.
Rodando uma celula isolada localmente¶
# Escolha o servico que voce quer reproduzir
export PROXMOX_SERVICE=pbs
# Puxe o mock correspondente e suba na porta padrao
docker pull "emersonfelipesp/proxmox-sdk:latest-${PROXMOX_SERVICE}"
docker run -d --name proxmox-e2e-mock -p 8006:8000 \
-e PROXMOX_API_MODE=mock \
"emersonfelipesp/proxmox-sdk:latest-${PROXMOX_SERVICE}" \
sh -c 'exec uvicorn ${APP_MODULE} --host 0.0.0.0 --port 8000'
# Rode a suite E2E do jeito que o CI roda
export PROXBOX_E2E_NETBOX_URL=http://127.0.0.1:8000
export PROXBOX_E2E_NETBOX_TOKEN=<seu token NetBox local>
export PROXMOX_MOCK_PUBLISHED_URL=http://localhost:8006
uv run pytest tests/e2e/ -m "mock_http" --tb=short -v
Em pbs ou pdm os modulos protegidos por requires_pve_schema reportam
SKIPPED [requires PVE schema; service=...] e o smoke em
test_proxmox_mock_health.py verifica que o container em execucao realmente
expoe a tag de servico solicitada.
Quando atualizar esta pagina¶
- Um novo valor de
proxmox_servicefor adicionado (ou removido) — atualize a tabela de tags e a tabela de verificacoes por celula. - Um novo helper de fixture ganhar parametro
service— liste em Camada de fixtures. - Um novo modulo de teste passar a depender (ou deixar de depender) do schema PVE — atualize Politica de skip.
- A protecao por marker do pytest mudar — atualize Markers pytest e cabos do CI.
Mantenha docs/development/e2e-proxmox-service-matrix.md sincronizado com este
arquivo quando o conteudo mudar.