Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IngressPerAppRequirer: retry when IP is not available #426

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 18 additions & 4 deletions lib/charms/traefik_k8s/v2/ingress.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def _on_ingress_revoked(self, event: IngressPerAppRevokedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 14
LIBPATCH = 15

PYDEPS = ["pydantic"]

Expand Down Expand Up @@ -338,6 +338,10 @@ class DataValidationError(IngressError):
"""Raised when data validation fails on IPU relation data."""


class IpNotAvailableError(IngressError):
"""Raised when retrieving ip information from juju failed."""


class _IngressPerAppBase(Object):
"""Base class for IngressPerUnit interface classes."""

Expand Down Expand Up @@ -710,7 +714,12 @@ def __init__(

def _handle_relation(self, event):
# created, joined or changed: if we have auto data: publish it
self._publish_auto_data()
try:
self._publish_auto_data()
except IpNotAvailableError as e:
log.error(e)
event.defer()
return
if self.is_ready():
# Avoid spurious events, emit only when there is a NEW URL available
new_url = (
Expand All @@ -728,7 +737,12 @@ def _handle_relation_broken(self, event):

def _handle_upgrade_or_leader(self, event):
"""On upgrade/leadership change: ensure we publish the data we have."""
self._publish_auto_data()
try:
self._publish_auto_data()
except IpNotAvailableError as e:
log.error(e)
event.defer()
return

def is_ready(self):
"""The Requirer is ready if the Provider has sent valid data."""
Expand Down Expand Up @@ -794,7 +808,7 @@ def _publish_unit_data(
):
ip = str(bind_address)
else:
log.error("failed to retrieve ip information from juju")
raise IpNotAvailableError("failed to retrieve ip information from juju")

unit_databag = relation.data[self.unit]
try:
Expand Down
19 changes: 18 additions & 1 deletion tests/scenario/test_ingress_per_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
IngressRequirerUnitData,
)
from ops import CharmBase, Framework
from scenario import Context, Model, Mount, Relation, State
from scenario import Context, Model, Mount, Network, Relation, State

from tests.scenario._utils import create_ingress_relation
from tests.scenario.conftest import MOCK_LB_ADDRESS
Expand Down Expand Up @@ -188,6 +188,23 @@ def test_ingress_per_app_requirer_with_auto_data(host, ip, port, model, evt_name
}


@pytest.mark.parametrize("evt_name", ("joined", "changed"))
@pytest.mark.parametrize("leader", (True, False))
def test_ingress_per_app_requirer_retry_without_ip(model, evt_name, leader):
ipa = Relation("ingress")
state = State(
model=model,
leader=leader,
relations=[ipa],
networks={"ingress": Network(bind_addresses=[], egress_subnets=[], ingress_addresses=[])},
)
requirer_ctx = get_requirer_ctx("10.0.0.1", None, 80)
state_out = requirer_ctx.run(getattr(ipa, evt_name + "_event"), state)

assert len(state_out.deferred) == 1
assert state_out.deferred[0].name == f"ingress_relation_{evt_name}"


def test_ingress_per_app_cleanup_on_remove(model, traefik_ctx, traefik_container):
"""Check that config file is removed when a relation is."""
ipa = create_ingress_relation()
Expand Down
12 changes: 3 additions & 9 deletions tests/unit/test_lib_per_app_requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
IngressPerAppReadyEvent,
IngressPerAppRequirer,
IngressPerAppRevokedEvent,
IpNotAvailableError,
)
from ops.charm import CharmBase
from ops.testing import Harness
Expand Down Expand Up @@ -157,12 +158,5 @@ def test_ipa_events_juju_binding_failure(self):
self.harness.begin_with_initial_hooks()
self.rel_id = self.harness.add_relation("ingress", "traefik-app")
self.harness.add_relation_unit(self.rel_id, "traefik-app/0")
self.harness.charm.ipa.provide_ingress_requirements(port=80)

self.assertEqual(
# None is default so it gets omitted
self.harness.get_relation_data(self.rel_id, "ipa-requirer/0").get(
"ip", "<OMITTED>"
),
"<OMITTED>",
)
with self.assertRaises(IpNotAvailableError):
self.harness.charm.ipa.provide_ingress_requirements(port=80)
Loading