Often, integration tests start with a cascade of deploy, wait-for-idle, add-relation, and wait-for-idle again. For example, in one of traefik’s itests we have:
async def test_build_and_deploy(ops_test: OpsTest, traefik_charm):
await asyncio.gather(
ops_test.model.deploy(
traefik_charm,
resources={
"traefik-image": METADATA["resources"]["traefik-image"]["upstream-source"]
},
application_name="traefik"),
ops_test.model.deploy(
"ch:prometheus-k8s",
application_name="prometheus",
channel="edge",
trust=True,
),
ops_test.model.deploy(
"ch:alertmanager-k8s",
application_name="alertmanager",
channel="edge",
trust=True,
),
ops_test.model.deploy(
"ch:grafana-k8s",
application_name="grafana",
channel="edge",
trust=True,
),
)
await ops_test.model.wait_for_idle(
status="active", timeout=600, idle_period=30, raise_on_error=False
)
await asyncio.gather(
ops_test.model.add_relation("prometheus:ingress", "traefik"),
ops_test.model.add_relation("alertmanager:ingress", "traefik"),
ops_test.model.add_relation("grafana:ingress", "traefik"),
)
await ops_test.model.wait_for_idle(status="active", timeout=600, idle_period=30)
We could potentially increase the congnitive ease by using a literal bundle:
async def test_build_and_deploy(ops_test: OpsTest, traefik_charm):
test_bundle = dedent(f"""
---
bundle: kubernetes
name: test-tls
applications:
traefik:
charm: {traefik_charm}
trust: true
resources:
traefik-image: {METADATA["resources"]["traefik-image"]["upstream-source"]}
prometheus:
charm: prometheus-k8s
trust: true
channel: edge
alertmanager:
charm: alertmanager-k8s
trust: true
channel: edge
grafana:
charm: grafana-k8s
trust: true
channel: edge
relations:
- [traefik:ingress, alertmanager:ingress]
- [traefik:ingress-per-unit, prometheus:ingress]
- [traefik:traefik-route, grafana:ingress]
""")
await deploy_literal_bundle(ops_test, test_bundle) # See appendix below
await ops_test.model.wait_for_idle(status="active", timeout=600, idle_period=30)
Pros
- All the information is captured in a standardized format – the bundle.
- Reduce the amount of the
gather/await
needed. - No need to concern ourselves with code ordering of
deploy
andadd_relation
. - Potentially better consistency (and less duplication) across multiple tests.
Cons
- Similar line count for both cases, but the literal yaml isn’t as easily programmable.
- Validating/linting the literal yaml isn’t starightforward, unlike regular code, for which we could have static checks.
Appendix
The deploy_literal_bundle
function is a workaround for ops_test.deploy_bundle
:
async def deploy_literal_bundle(ops_test: OpsTest, bundle: str):
run_args = [
"juju",
"deploy",
"--trust",
"-m",
ops_test.model_name,
str(ops_test.render_bundle(bundle)),
]
retcode, stdout, stderr = await ops_test.run(*run_args)
assert retcode == 0, f"Deploy failed: {(stderr or stdout).strip()}"
logger.info(stdout)