Support juju 4 in integration tests - #596
Conversation
juju ssh fails on Juju 4 with 'Permission denied (publickey)' due to changed SSH key provisioning timing. Replace all ssh calls that read files on units with juju.exec(..., unit=...).stdout, which routes through the Juju agent and requires no SSH keypair. Affected tests: test_haproxy_route, test_haproxy_route_tcp (x4), test_haproxy_ddos, and the apache2 log helper.
Dependency charms (postgresql 16/edge, postgresql-k8s 14/edge, hydra, kratos, traefik-k8s, identity-platform-login-ui-operator, self-signed-certificates) declare 'assumes: juju < 4.0.0' (or < 3.5.0), blocking deployment on Juju 4. Juju 4's deploy validator skips assumes checks when --force is passed. Add force=True to all external charm deploys in test fixtures so the assumes gate is bypassed without changing the charm revisions. Affected fixtures: postgresql_fixture, deploy_iam_bundle_fixture, certificate_provider_application_fixture (both top-level and haproxy-operator conftest).
| juju.wait( | ||
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, haproxy_route_requirer | ||
| ) | ||
| ), | ||
| ) |
There was a problem hiding this comment.
nit
| juju.wait( | |
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, haproxy_route_requirer | |
| ) | |
| ), | |
| ) | |
| juju.wait( | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, haproxy_route_requirer | |
| ) | |
| ), | |
| ) |
| juju.wait( | ||
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, any_charm_haproxy_route_tcp_requirer | ||
| ) | ||
| ), | ||
| ) |
There was a problem hiding this comment.
nit
| juju.wait( | |
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_tcp_requirer | |
| ) | |
| ), | |
| ) | |
| juju.wait( | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_tcp_requirer | |
| ) | |
| ), | |
| ) |
|
|
||
| deadline = time.monotonic() + 300 | ||
| hashed_tcp_client = None | ||
| while time.monotonic() < deadline: | ||
| with ( | ||
| socket.create_connection((str(haproxy_ip_address), 4444), timeout=30) as sock, | ||
| context.wrap_socket(sock, server_hostname="example.com") as secure_socket, | ||
| ): | ||
| secure_socket.sendall(b"ping") | ||
| assert b"pong" in secure_socket.read() | ||
| field = _last_client_field(juju, unit, "haproxy_route_tcp_4444", required=False) | ||
| if field is not None and not _is_plaintext_address(field): | ||
| hashed_tcp_client = field | ||
| break | ||
| time.sleep(5) | ||
| assert hashed_tcp_client is not None, "no hashed client field found in haproxy TCP logs" |
| def _is_plaintext_address(field: str) -> bool: | ||
| """Return whether a logged client field is a plaintext IP:port entry.""" | ||
| try: | ||
| ip_address(field.rsplit(":", 1)[0]) | ||
| except ValueError: | ||
| return False | ||
| return True |
There was a problem hiding this comment.
not sure if relevant
but should we validate the port?
i assume something like 127.0.0.1: would pass validation
| deadline = time.monotonic() + timeout | ||
| while time.monotonic() < deadline: | ||
| marker = f"{marker_prefix}-{uuid.uuid4().hex}" | ||
| response = requests.get(f"{address}/?{marker}", verify=False, timeout=30) # nosec | ||
| assert response.status_code == 200 | ||
| field = _last_client_field(juju, unit, marker, required=False) | ||
| if field is not None and _is_plaintext_address(field) == expect_plaintext: | ||
| return field | ||
| time.sleep(5) | ||
| raise TimeoutError( | ||
| f"no {'plaintext' if expect_plaintext else 'hashed'} client field found" | ||
| f" in haproxy logs within {timeout}s" | ||
| ) |
| juju.wait( | ||
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | ||
| ) | ||
| ), | ||
| ) |
There was a problem hiding this comment.
nit
| juju.wait( | |
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), | |
| ) | |
| juju.wait( | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), | |
| ) |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | ||
| ) | ||
| ), | ||
| ) |
There was a problem hiding this comment.
Unless I misunderstood previous suggestions from Fouad, they may apply here too?
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | ||
| ) | ||
| ), |
There was a problem hiding this comment.
nit
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), |
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | ||
| ) | ||
| ), |
There was a problem hiding this comment.
nit
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, any_charm_haproxy_route_requirer | |
| ) | |
| ), |
| juju.deploy( | ||
| "postgresql", | ||
| app=POSTGRESQL_APPLICATION, | ||
| channel="16/edge", | ||
| base="ubuntu@24.04", | ||
| force=True, |
There was a problem hiding this comment.
out of curiosity
why do we need force?
also what is the reason for deploy to have a force option in the first place?
| lambda status: ( | ||
| status.apps[configured_application_with_tls].is_blocked | ||
| and jubilant.all_agents_idle( | ||
| status, configured_application_with_tls, HAPROXY_ROUTE_REQUIRER_NAME | ||
| ) | ||
| ), | ||
| timeout=10 * 60, |
There was a problem hiding this comment.
nit
| lambda status: ( | |
| status.apps[configured_application_with_tls].is_blocked | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, HAPROXY_ROUTE_REQUIRER_NAME | |
| ) | |
| ), | |
| timeout=10 * 60, | |
| lambda status: ( | |
| jubilant.all_blocked(status, configured_application_with_tls) | |
| and jubilant.all_agents_idle( | |
| status, configured_application_with_tls, HAPROXY_ROUTE_REQUIRER_NAME | |
| ) | |
| ), | |
| timeout=10 * 60, |
| artifacts.build.yaml | ||
|
|
||
| .worktrees | ||
| **/superpowers/** No newline at end of file |
| k8s: | ||
| enable: true | ||
| bootstrap: false | ||
| channel: 1.32-classic/stable |
There was a problem hiding this comment.
Any reason to not be on 1.35-classic/stable?
What this PR does
juju.ssh(unit, cmd)/cli("ssh", ...)calls that read files on units withjuju.exec(cmd, unit=...).stdout.