diff --git a/juju/application.py b/juju/application.py index 5f824e3a..e1e08ef2 100644 --- a/juju/application.py +++ b/juju/application.py @@ -4,17 +4,20 @@ import hashlib import json import logging +import typing from pathlib import Path -from . import model, tag, utils, jasyncio -from .url import URL -from .status import derive_status +from . import jasyncio, model, tag, utils from .annotationhelper import _get_annotations, _set_annotations -from .client import client -from .errors import JujuError, JujuApplicationConfigError from .bundle import get_charm_series, is_local_charm -from .placement import parse as parse_placement +from .client import client +from .errors import JujuApplicationConfigError, JujuError from .origin import Channel, Source +from .placement import parse as parse_placement +from .relation import Relation +from .status import derive_status +from .url import URL +from .utils import block_until log = logging.getLogger(__name__) @@ -59,14 +62,14 @@ def subordinate_units(self): return [u for u in self.units if u.is_subordinate] @property - def relations(self): + def relations(self) -> typing.List[Relation]: return [rel for rel in self.model.relations if rel.matches(self.name)] def related_applications(self, endpoint_name=None): apps = {} for rel in self.relations: if rel.is_peer: - local_ep, remote_ep = rel.endpoints[0] + local_ep, remote_ep = rel.endpoints else: def is_us(ep): return ep.application.name == self.name @@ -191,12 +194,13 @@ async def scale(self, scale=None, scale_change=None): scale_change=scale_change) ]) - async def destroy_relation(self, local_relation, remote_relation): + async def destroy_relation(self, local_relation, remote_relation, block_until_done: bool = False): """Remove a relation to another application. :param str local_relation: Name of relation on this application :param str remote_relation: Name of relation on the other application in the form '[:]' + :param bool block_until_done: Wait until the relation is completely removed. """ if ':' not in local_relation: @@ -207,8 +211,16 @@ async def destroy_relation(self, local_relation, remote_relation): log.debug( 'Destroying relation %s <-> %s', local_relation, remote_relation) - return await app_facade.DestroyRelation(endpoints=[ + await app_facade.DestroyRelation(endpoints=[ local_relation, remote_relation]) + if block_until_done: + await block_until( + lambda: not any( + relation.matches(local_relation, remote_relation) + for relation in self.relations + ) + ) + remove_relation = destroy_relation async def destroy_unit(self, *unit_names): diff --git a/tests/integration/test_application.py b/tests/integration/test_application.py index abbb8be9..0ab2f439 100644 --- a/tests/integration/test_application.py +++ b/tests/integration/test_application.py @@ -1,16 +1,18 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import logging from pathlib import Path import asyncio import pytest -import logging -from .. import base -from juju import jasyncio, errors -from juju.url import URL, Schema +from juju import errors, jasyncio +from juju.application import Application from juju.client import client +from juju.url import URL, Schema + +from .. import base MB = 1 @@ -342,3 +344,14 @@ async def test_app_charm_name(): await model.wait_for_idle(status="active") assert 'ubuntu' in app.charm_url assert 'ubuntu' == app.charm_name + + +@base.bootstrapped +async def test_app_relation_destroy_block_until_done(): + async with base.CleanModel() as model: + app: Application = await model.deploy('docker-registry') + rsa: Application = await model.deploy("easyrsa") + relation = await app.relate('cert-provider', rsa.name) + await model.wait_for_idle(status="active") + await app.destroy_relation('cert-provider', rsa.name, block_until_done=True) + assert relation not in app.relations