Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
320f069
test: replace juju ssh with juju exec to fix Juju 4 key auth failures
Thanhphan1147 Jul 2, 2026
fa9e27d
test: bypass assumes Juju version checks with force=True for Juju 4
Thanhphan1147 Jul 3, 2026
7912111
Merge branch 'main' into migrate-integration-tests-juju4
Thanhphan1147 Aug 12, 2026
e1325e5
move concierge to juju 4.1/edge
Thanhphan1147 Aug 12, 2026
139adc4
test(spoe): replace juju ssh with juju exec for Juju 4 compatibility
Thanhphan1147 Aug 12, 2026
7fae349
test: replace juju scp with exec-based cert injection for Juju 4
Thanhphan1147 Aug 12, 2026
1aa8bbc
test(route-policy): add force=True to store charm deploys for Juju 4
Thanhphan1147 Aug 12, 2026
3ffef08
change juju.ssh to juju.exec
Thanhphan1147 Aug 13, 2026
3ab1eb4
fix fmt for haproxy-route-policy
Thanhphan1147 Aug 13, 2026
b31ea66
test: pass rpc action kwargs through kwargs param for Juju 4 compatib…
Thanhphan1147 Aug 13, 2026
d841cc4
Fix integration tests for juju 4 compatibility
Thanhphan1147 Aug 13, 2026
71174e8
Merge branch 'main' into migrate-integration-tests-juju4
Thanhphan1147 Aug 14, 2026
a654ff1
Merge branch 'main' into migrate-integration-tests-juju4
Thanhphan1147 Aug 31, 2026
7db71a1
fix(tests): make integration tests reliable on Juju 4
Thanhphan1147 Aug 31, 2026
38a7248
use "ingres-address" and setup juju 3 in spread for spoe tests.
Thanhphan1147 Sep 2, 2026
0f9821f
Merge branch 'main' into migrate-integration-tests-juju4
Thanhphan1147 Sep 2, 2026
bf986a4
Merge remote-tracking branch 'origin/main' into migrate-integration-t…
Thanhphan1147 Sep 2, 2026
82f22e1
remove unnecessary helpers
Thanhphan1147 Sep 2, 2026
90d6e37
add more aggressive wait checks to account for juju 4 behavior
Thanhphan1147 Sep 3, 2026
a516330
run hacluster tests in juju3, wait more aggressively for legacy tests
Thanhphan1147 Sep 3, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ haproxy-route-policy/.python-version
artifacts.build.yaml

.worktrees
**/superpowers/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newline

22 changes: 22 additions & 0 deletions concierge-ck8s-juju3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2026 Canonical Ltd.
# See LICENSE file for licensing details.

juju:
channel: 3/stable
model-defaults:
test-mode: "true"
automatically-retry-hooks: "false"

providers:
lxd:
enable: true
bootstrap: true
k8s:
enable: true
bootstrap: false
channel: 1.32-classic/stable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not be on 1.35-classic/stable?

features:
load-balancer:
enabled: true
l2-mode: true
cidrs: 10.43.45.0/24
2 changes: 1 addition & 1 deletion concierge-ck8s.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See LICENSE file for licensing details.

juju:
channel: 3/stable
channel: 4.1/edge
model-defaults:
test-mode: "true"
automatically-retry-hooks: "false"
Expand Down
19 changes: 19 additions & 0 deletions concierge-lxd-juju3.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright 2026 Canonical Ltd.
# See LICENSE file for licensing details.

juju:
channel: 3/stable
model-defaults:
test-mode: "true"
automatically-retry-hooks: "false"

providers:
lxd:
enable: true
bootstrap: true

host:
snaps:
charmcraft:
channel: latest/stable
classic: true
2 changes: 1 addition & 1 deletion concierge-lxd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# See LICENSE file for licensing details.

juju:
channel: 3/stable
channel: 4.1/edge
model-defaults:
test-mode: "true"
automatically-retry-hooks: "false"
Expand Down
492 changes: 0 additions & 492 deletions haproxy-ddos-protection-configurator/uv.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion haproxy-operator/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ def _get_peer_units_address(self) -> list[str]:
for unit in haproxy_peer_integration.units:
if unit != self.unit:
if peer_unit_address := haproxy_peer_integration.data[unit].get(
"private-address"
"ingress-address"
):
peer_units_address.append(peer_unit_address)
else:
Expand Down
9 changes: 8 additions & 1 deletion haproxy-operator/src/http_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,14 @@ def _on_relation_joined(self, event: RelationJoinedEvent) -> None:
event: relation-changed event.
"""
event.relation.data[self.charm.unit].update(
{"hostname": self.bind_address, "port": f"{DEFAULT_HAPROXY_PORT}"}
{
"hostname": self.bind_address,
"port": f"{DEFAULT_HAPROXY_PORT}",
# Juju >= 4.0 no longer populates `private-address` in unit
# relation data automatically. Write it explicitly so legacy
# charms integrating over the http interface keep working.
"private-address": self.bind_address,
}
)

# We add a placeholder implementation of this method because of parent class
Expand Down
4 changes: 2 additions & 2 deletions haproxy-operator/src/state/haproxy_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,9 @@ def from_requirer(
provider_data = relation.load(HaproxyRoutePolicyProviderAppData, relation.app)
provider_unit_addresses = [
# explicitly cast to IPvAnyAddress because we already filtered out None values.
cast(IPvAnyAddress, relation.data[unit].get("private-address"))
cast(IPvAnyAddress, unit_address)
for unit in relation.units
if relation.data[unit].get("private-address") is not None
if (unit_address := relation.data[unit].get("ingress-address")) is not None
]
return cls(
policy_backend_port=provider_data.policy_backend_port,
Expand Down
13 changes: 12 additions & 1 deletion haproxy-operator/tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ def certificate_provider_application_fixture(
logger.warning("Using existing application: %s", SELF_SIGNED_CERTIFICATES_APP_NAME)
return SELF_SIGNED_CERTIFICATES_APP_NAME
juju.deploy(
"self-signed-certificates", app=SELF_SIGNED_CERTIFICATES_APP_NAME, channel="1/edge"
"self-signed-certificates",
app=SELF_SIGNED_CERTIFICATES_APP_NAME,
channel="1/edge",
force=True,
)
return SELF_SIGNED_CERTIFICATES_APP_NAME

Expand Down Expand Up @@ -334,6 +337,14 @@ def haproxy_route_tcp_relation_fixture(
f"{configured_application_with_tls}:haproxy-route-tcp",
any_charm_haproxy_route_tcp_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
)
),
)
Comment on lines +340 to +347

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
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
)
),
)

juju.run(
f"{any_charm_haproxy_route_tcp_requirer}/0",
"rpc",
Expand Down
6 changes: 3 additions & 3 deletions haproxy-operator/tests/integration/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ def get_http_version_from_apache2_logs(
Returns:
The extracted HTTP version string.
"""
last_httpx_log_entry = juju.ssh(
f"{app_name}/0",
last_httpx_log_entry = juju.exec(
f"grep '{request_id}' /var/log/apache2/access.log | tail -n 1",
)
unit=f"{app_name}/leader",
).stdout

match = pattern.search(last_httpx_log_entry)
if not match:
Expand Down
4 changes: 3 additions & 1 deletion haproxy-operator/tests/integration/legacy/test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ def test_get_certificate_action(
)
assert "-----BEGIN CERTIFICATE-----" in task.results.get("certificate", "")

stdout = juju.ssh(f"{configured_application_with_tls}/0", "ls /var/lib/haproxy/certs")
stdout = juju.exec(
"ls /var/lib/haproxy/certs", unit=f"{configured_application_with_tls}/0"
).stdout
assert f"{TEST_EXTERNAL_HOSTNAME_CONFIG}.pem" in stdout

# The action should fail when run without the required hostname parameter.
Expand Down
2 changes: 1 addition & 1 deletion haproxy-operator/tests/integration/legacy/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ def test_config(application: str, juju: jubilant.Juju):
juju.config(application, {"global-maxconn": "1024"})
juju.wait(lambda status: jubilant.all_active(status, application))

stdout = juju.ssh(f"{application}/0", "cat /etc/haproxy/haproxy.cfg")
stdout = juju.exec("cat /etc/haproxy/haproxy.cfg", unit=f"{application}/0").stdout
assert "maxconn 1024" in stdout
2 changes: 1 addition & 1 deletion haproxy-operator/tests/integration/legacy/test_cos.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ def test_metrics(application: str, juju: jubilant.Juju):
act: request haproxy metrics endpoint via SSH.
assert: confirm that metrics are available.
"""
stdout = juju.ssh(f"{application}/0", "curl -m 10 localhost:8404/metrics")
stdout = juju.exec("curl -m 10 localhost:8404/metrics", unit=f"{application}/0").stdout
assert "haproxy_backend_max_connect_time_seconds" in stdout
12 changes: 10 additions & 2 deletions haproxy-operator/tests/integration/legacy/test_haproxy_route.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
from requests import Session

from .conftest import TEST_EXTERNAL_HOSTNAME_CONFIG, get_unit_ip_address
from .conftest import TEST_EXTERNAL_HOSTNAME_CONFIG, all_active_and_idle, get_unit_ip_address
from .helper import DNSResolverHTTPSAdapter

HAPROXY_ROUTE_REQUIRER_HOSTNAME = f"ok.{TEST_EXTERNAL_HOSTNAME_CONFIG}"
Expand All @@ -28,10 +28,18 @@ def test_haproxy_route_integration(
f"{configured_application_with_tls}:haproxy-route",
f"{haproxy_route_requirer}:require-haproxy-route",
)
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
)
),
)
Comment on lines +31 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
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.run(f"{haproxy_route_requirer}/0", "rpc", {"method": "update_relation"})

juju.wait(
lambda status: jubilant.all_active(
lambda status: all_active_and_idle(
status, configured_application_with_tls, haproxy_route_requirer
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest
import requests

from .conftest import get_unit_address
from .conftest import all_active_and_idle, get_unit_address


@pytest.mark.abort_on_fail
Expand All @@ -27,8 +27,17 @@ def test_reverseproxy_relation(
juju.run(f"{any_charm_requirer}/0", "rpc", {"method": "start_server"})

juju.integrate(f"{application}:reverseproxy", any_charm_requirer)
juju.wait(
lambda status: (
any(
relation.related_app == application
for relation in status.apps[any_charm_requirer].relations.get("provide-http", [])
)
and jubilant.all_agents_idle(status, application, any_charm_requirer)
)
)
juju.run(f"{any_charm_requirer}/0", "rpc", {"method": "update_relation_data"})
juju.wait(lambda status: jubilant.all_active(status, application, any_charm_requirer))
juju.wait(lambda status: all_active_and_idle(status, application, any_charm_requirer))

unit_address = get_unit_address(juju, application)

Expand Down
16 changes: 14 additions & 2 deletions haproxy-operator/tests/integration/legacy/test_website.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pytest
import requests

from .conftest import get_unit_address
from .conftest import all_active_and_idle, get_unit_address


@pytest.mark.abort_on_fail
Expand All @@ -22,7 +22,19 @@ def test_website_relation(
assert: Requests to reverseproxy-requirer return the default haproxy page.
"""
juju.integrate(f"{application}:website", f"{reverseproxy_requirer}:reverseproxy")
juju.wait(lambda status: jubilant.all_active(status, application, reverseproxy_requirer))
juju.wait(
lambda status: (
any(
relation.related_app == application
for relation in status.apps[reverseproxy_requirer].relations.get(
"reverseproxy", []
)
)
and all_active_and_idle(status, application, reverseproxy_requirer)
),
delay=5,
successes=6,
)

unit_address = get_unit_address(juju, reverseproxy_requirer)
response = requests.get(unit_address, timeout=5)
Expand Down
16 changes: 14 additions & 2 deletions haproxy-operator/tests/integration/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import jubilant
import pytest

from .conftest import all_active_and_idle


@pytest.mark.abort_on_fail
def test_action(
Expand All @@ -23,6 +25,14 @@ def test_action(
juju.integrate(
f"{configured_application_with_tls}:haproxy-route", any_charm_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_requirer
)
),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I misunderstood previous suggestions from Fouad, they may apply here too?


juju.run(
f"{any_charm_haproxy_route_requirer}/0",
Expand All @@ -43,7 +53,7 @@ def test_action(
},
)
juju.wait(
lambda status: jubilant.all_active(
lambda status: all_active_and_idle(
status, configured_application_with_tls, any_charm_haproxy_route_requirer
)
)
Expand Down Expand Up @@ -79,7 +89,9 @@ def test_action(
assert task.results == {"endpoints": "[]"}, task.results

# get-configuration returns exactly the configuration currently on disk.
on_disk = juju.ssh(f"{configured_application_with_tls}/0", "cat /etc/haproxy/haproxy.cfg")
on_disk = juju.exec(
"cat /etc/haproxy/haproxy.cfg", unit=f"{configured_application_with_tls}/0"
).stdout
task = juju.run(f"{configured_application_with_tls}/0", "get-configuration")
assert task.results["source"] == "disk", task.results
assert task.results["configuration"].splitlines() == on_disk.splitlines(), task.results
Expand Down
Loading
Loading